Skip to content

Commit 434fdf0

Browse files
committed
Add solutions to the basic libraries chapter and introduce benchmark
suites
1 parent c41812c commit 434fdf0

File tree

7 files changed

+205
-12
lines changed

7 files changed

+205
-12
lines changed

README.md

+30-12
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
[![CircleCI](https://circleci.com/gh/BoeingX/haskell-programming-from-first-principles/tree/master.svg?style=svg)](https://circleci.com/gh/BoeingX/haskell-programming-from-first-principles/tree/master)
44
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
55

6-
7-
**Disclaimer: this is an on-going project and is subject to frequent changes.**
8-
96
This repository hosts my notes and solutions to exercises in the book
107
[Haskell Programming from First Principles](http://haskellbook.com/).
118

@@ -16,17 +13,28 @@ Once setup, Stack takes care of any Haskell package dependencies.
1613

1714
## Project structure
1815

19-
This repository is organized as a single Stack project.
20-
Exercise(s) solutions can be found at
21-
```bash
22-
src/<chapter name>/<section name>/<exercise name>.hs
16+
This repository is organized as a single Stack project as follows
2317
```
24-
Companion test suites (if available) can be found at
25-
```bash
26-
test/<chapter name>/<section name>/<exercise name>Spec.hs
18+
.
19+
├── benchmark/
20+
├── ChangeLog.md
21+
├── haskell-programming-from-first-principles.cabal
22+
├── LICENSE
23+
├── package.yaml
24+
├── README.md
25+
├── Setup.hs
26+
├── src/
27+
├── stack.yaml
28+
└── test/
29+
```
30+
where `[src](./src)` are solutions whose name follows the pattern
31+
```
32+
ChapterName/SectionName/ExerciseName.hs
2733
```
34+
[test](./test/) and [benchmark](./benchmark) are test and benchmark suites following
35+
the same naming convention.
2836

29-
## Run test
37+
## Run tests
3038

3139
All test suites can be discovered by `hspec-discover`. To run tests, simply do
3240

@@ -39,4 +47,14 @@ stack --fast test
3947
```
4048
if you want avoid GHC optimization (hence faster).
4149

42-
## Troubleshooting
50+
## Run benchmarks
51+
52+
Run
53+
54+
```bash
55+
stack bench
56+
```
57+
58+
> **Never** use the `--fast` flag for benchmarks otherwise you will get **wrong** results.
59+
60+
Reference benchmark results are included in each benchmark file as block comment.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module BasicLibraries.ChapterExercises.DifferenceListBenchmark (
2+
benchmarks
3+
) where
4+
5+
import Criterion
6+
import BasicLibraries.ChapterExercises.DifferenceList
7+
8+
schlemiel :: Int -> [Int]
9+
schlemiel i = go i []
10+
where go 0 xs = xs
11+
go n xs = go (n-1) [n] ++ xs
12+
13+
constructDlist :: Int -> [Int]
14+
constructDlist i = toList $ go i empty
15+
where go 0 xs = xs
16+
go n xs = go (n-1) (singleton n `append` xs)
17+
18+
benchmarks :: [Benchmark]
19+
benchmarks =
20+
[ bench "concat list" $ whnf schlemiel 123456
21+
, bench "concat dlist" $ whnf constructDlist 123456
22+
]
23+
24+
{- Reference benchmark results
25+
26+
benchmarking Difference List/concat list
27+
time 35.99 ms (32.95 ms .. 42.01 ms)
28+
0.942 R² (0.873 R² .. 0.993 R²)
29+
mean 38.50 ms (36.79 ms .. 41.26 ms)
30+
std dev 4.482 ms (3.416 ms .. 5.551 ms)
31+
variance introduced by outliers: 45% (moderately inflated)
32+
33+
benchmarking Difference List/concat dlist
34+
time 217.9 μs (211.2 μs .. 225.5 μs)
35+
0.990 R² (0.984 R² .. 0.996 R²)
36+
mean 217.9 μs (213.2 μs .. 224.9 μs)
37+
std dev 19.38 μs (15.17 μs .. 23.67 μs)
38+
variance introduced by outliers: 75% (severely inflated)
39+
-}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module BasicLibraries.ChapterExercises.QueueBenchmark (
2+
benchmarks
3+
) where
4+
5+
import Criterion
6+
import Data.Sequence
7+
import BasicLibraries.ChapterExercises.Queue
8+
9+
listQueueBench :: Int -> [Int]
10+
listQueueBench n = foldr go [] [1..2*n]
11+
where go x acc
12+
| even x = x : acc
13+
| otherwise = init acc
14+
15+
queueBench :: Int -> Queue Int
16+
queueBench n = foldr go initQueue [1..2*n]
17+
where initQueue = Queue [] []
18+
go x acc
19+
| even x = push x acc
20+
| otherwise = let Just (_, acc') = pop acc in acc'
21+
22+
sequenceBench :: Int -> Seq Int
23+
sequenceBench n = foldr go empty [1..2*n]
24+
where go x acc
25+
| even x = x <| acc
26+
| otherwise = let acc' :> _ = viewr acc in acc'
27+
28+
benchmarks :: [Benchmark]
29+
benchmarks =
30+
[ bench "list queue" $ nf listQueueBench 123456
31+
, bench "queue" $ whnf queueBench 123456
32+
, bench "sequence" $ whnf sequenceBench 123456
33+
]
34+
35+
{- Reference benchmark results
36+
37+
benchmarking Queue/list queue
38+
time 27.27 ms (26.40 ms .. 27.86 ms)
39+
0.996 R² (0.991 R² .. 0.999 R²)
40+
mean 27.70 ms (27.21 ms .. 28.66 ms)
41+
std dev 1.439 ms (768.4 μs .. 2.317 ms)
42+
variance introduced by outliers: 16% (moderately inflated)
43+
44+
benchmarking Queue/queue
45+
time 10.75 ms (10.51 ms .. 11.01 ms)
46+
0.996 R² (0.993 R² .. 0.998 R²)
47+
mean 10.98 ms (10.83 ms .. 11.27 ms)
48+
std dev 545.3 μs (329.6 μs .. 914.8 μs)
49+
variance introduced by outliers: 23% (moderately inflated)
50+
51+
benchmarking Queue/sequence
52+
time 7.768 ms (7.597 ms .. 7.926 ms)
53+
0.996 R² (0.993 R² .. 0.999 R²)
54+
mean 8.119 ms (7.932 ms .. 8.509 ms)
55+
std dev 719.3 μs (393.4 μs .. 1.096 ms)
56+
variance introduced by outliers: 51% (severely inflated)
57+
58+
-}

benchmark/Main.hs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module Main where
2+
3+
import Criterion.Main
4+
5+
import qualified BasicLibraries.ChapterExercises.DifferenceListBenchmark
6+
import qualified BasicLibraries.ChapterExercises.QueueBenchmark
7+
8+
main :: IO ()
9+
main = defaultMain
10+
[
11+
bgroup "Difference List" BasicLibraries.ChapterExercises.DifferenceListBenchmark.benchmarks
12+
, bgroup "Queue" BasicLibraries.ChapterExercises.QueueBenchmark.benchmarks
13+
]

package.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ dependencies:
4040
library:
4141
source-dirs: src
4242

43+
benchmarks:
44+
haskell-programming-from-first-principles-benchmark:
45+
dependencies:
46+
- base
47+
- criterion
48+
ghc-options:
49+
- -O2
50+
- -rtsopts
51+
- -threaded
52+
- -with-rtsopts=-N
53+
main: Main.hs
54+
source-dirs:
55+
- src
56+
- benchmark
57+
4358
tests:
4459
haskell-programming-from-first-principles-test:
4560
main: Spec.hs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module BasicLibraries.ChapterExercises.DifferenceList where
2+
3+
newtype DList a = DL { unDL :: [a] -> [a] }
4+
5+
empty :: DList a
6+
empty = DL id
7+
{-# INLINE empty #-}
8+
9+
singleton :: a -> DList a
10+
singleton a = DL $ const [a]
11+
{-# INLINE singleton #-}
12+
13+
toList :: DList a -> [a]
14+
toList xs = unDL xs []
15+
{-# INLINE toList #-}
16+
17+
-- Prepend a single element to a dlist.
18+
infixr `cons`
19+
cons :: a -> DList a -> DList a
20+
cons x xs = DL ((x:) . unDL xs)
21+
{-# INLINE cons #-}
22+
23+
-- Append a single element to a dlist.
24+
infixl `snoc`
25+
snoc :: DList a -> a -> DList a
26+
snoc xs x = DL (unDL xs . (x:))
27+
{-# INLINE snoc #-}
28+
29+
-- Append dlists.
30+
append :: DList a -> DList a -> DList a
31+
append xs ys = DL $ unDL xs . unDL ys
32+
{-# INLINE append #-}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module BasicLibraries.ChapterExercises.Queue where
2+
3+
-- From Okasaki's Purely Functional Data Structures
4+
data Queue a =
5+
Queue { enqueue :: [a]
6+
, dequeue :: [a]
7+
} deriving (Eq, Show)
8+
9+
-- adds an item
10+
push :: a -> Queue a -> Queue a
11+
push x (Queue [] []) = Queue [] [x]
12+
push x (Queue xs ys) = Queue (x : xs) ys
13+
14+
pop :: Queue a -> Maybe (a, Queue a)
15+
pop (Queue [] []) = Nothing
16+
pop (Queue xs []) = Just (y, Queue [] ys)
17+
where (y : ys) = reverse xs
18+
pop (Queue xs (y : ys)) = Just (y, Queue xs ys)

0 commit comments

Comments
 (0)