-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path03_extra.qmd
207 lines (125 loc) · 6 KB
/
03_extra.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
---
title: 3. Finishing Up
---
Good job working your way through the list!
Here are some final links, tips and bonus exercises.
## Hoogle
As a next step you should look at Hoogle:
#### <https://hoogle.haskell.org>
It's a great tool for you to search for already existing functions.
Try searching for the type of one of the most fundamental Haskell functions:
```{.haskell}
(b -> c) -> (a -> b) -> a -> c
```
Or you could search for something simpler:
```{.haskell}
[a] -> [a] -> [a]
```
Learning to think about how you want to transform your data *on the type level* really is key to mastering Haskell.
## "Operators" and Lambdas {#sec-operators}
In Haskell operators are functions.
This means that `+` and `-` are functions just like you would declare your own `add`.
This means that you can declare your own operators, which the text parsing library has taken quite a few liberties when doing, as can be seen in this example from the book [Real World Haskell, available for free online](https://book.realworldhaskell.org/read/using-parsec.html):
```{.haskell}
-- file: ch16/HttpRequestParser.hs
p_headers :: CharParser st [(String, String)]
p_headers = header `manyTill` crlf
where header = liftA2 (,) fieldName (char ':' *> spaces *> contents)
contents = liftA2 (++) (many1 notEOL <* crlf)
(continuation <|> pure [])
continuation = liftA2 (:) (' ' <$ many1 (oneOf " \t")) contents
fieldName = (:) <$> letter <*> many fieldChar
fieldChar = letter <|> digit <|> oneOf "-_"
```
:::{.column-margin}
**PRO TIP:** Don't try to understand this piece of code, instead marvel at the seemingly endless onslaught of combining operators.
:::
Let's toy around with it a bit, to see how it works.
This is three definitions of the same thing.
```{.haskell}
subtractTwo :: Int -> Int
subtractTwo x = x - 2 -- <1>
subtractTwo = \x -> x - 2 -- <2>
subtractTwo = (- 2) -- <3>
```
1. Pretty normal implementation.
2. Using a lambda expression. The `\` is the $\lambda$ (lambda) sign, which declares that `x` is a named function argument, allowing it to be used to the right side of the `->`.
3. Partial application of the `(-)` function. Had it instead been `(2 -)` the the partial application of minus would have bound up the left side argument. Still valid, but that's the function `subtractFromTwo`.
Go wild with `:t` to understand how this works.
If you find an operator you want to implement you can take some inspiration from the Parsec code example above.
Say that you want to make your own version of `(.)`^[Called "ball" or "ring" or "function composition". It's how function applications are chained together with nicer syntax than a bazillion parenthenses. Usually you write `p . q . f . g . h $ x`, where `($)` is another operator that is special in how "hard" it binds/is applied. See below.] then just slam some GT/LT signs around that, to do your own `my`-operator:
```{.haskell}
(<.>) :: (b -> c) -> (a -> b) -> a -> c
f <.> g = \x -> undefined
```
### Stupid but Valid Haskell
```{.haskell}
ghci> let x <╯°□°>╯ y = x + y
```
## Next Level `:t`
There's another command you can do in `ghci` which is `:i` for info.
The footnote above mentions that `($)` is special in how hard it binds.
How's that quantified?
If you did `:t ($)` you probably got nothing.
Here's how you do it instead:
```{.haskell}
ghci> :i ($)
($) :: (a -> b) -> a -> b -- Defined in ‘GHC.Base’
infixr 0 $
ghci> :i (.)
(.) :: (b -> c) -> (a -> b) -> a -> c -- Defined in ‘GHC.Base’
infixr 9 .
```
The `:i` command is also a tool for exploring concepts "higher" than functions, such as datatypes and type classes.
If you did `:i (+)` you noticed it said something about `class Num`.
Try looking at that:
```{.haskell}
ghci> :i Num
```
:::{.column-margin}
If you do `:i Foldable` it might make more sense than it would have when you started out.
:::
It declares "To be called a `Num` all these functions must be implemented." as well as a list of all currently loaded datatypes which implements the `Num` class.
You can even look at the declaration datatypes, such as the list and `Maybe`!
```{.haskell}
ghci> :i []
ghci> :i Maybe
```
## Bonus Exercises
### Difficult List Functions
Write your own version of `mapAccumL`.
Building on that, a final, quite difficult exercise:
:::{.panel-tabset}
## factorialPairs
Make a function (constant) which is a list of all numbers and the factorial of them.
So all pairs $(n, n!)$ in one big list.
```{.haskell}
factorialPairs :: [(Integer, Integer)]
```
Take a close look at the `mapAccum` functions.
The `s` in the type signature is for "State".
One great use would be to take list `[1..]` and use `mapAccumL` (will only work with the `L` version) to very quickly produce a list of pairs containing `n` and `fact(n)`.
Remember that small function `snd`? Should fit right in as your outermost function call, to get rid of the paired state variable.
## source
```{.haskell}
factorialPairs :: [(Integer, Integer)]
factorialPairs = snd $ mapAccumL (\fn n -> let fn' = n*fn in (fn', (n,fn'))) 1 [1..]
```
:::
### Difficult Search Tree Library Glue-ing
Import `Data.Map`^[The standard binary tree implementation. Import it with `import qualified Data.Map as M` in order to avoid naming conflicts.] into your program and create a one-liner (or as close as you can make it) to a tree building function:
```{.haskell}
buildTree :: (Ord k) => [(k,v)] -> Map k v
```
It should be done by using a `fold` (list version) with `M.insert` as its function argument.
Look at `M.empty` as your *zero* argument.
Search for `Data.Map` on Hoogle^[Once you reach Hoogle the page for `Data.Map` click on `Data.Map.Strict` to get the full list of available functions.] for more information.
## That's All!
Good luck with your course work!
:::{.column-margin}
#### Done?
Was it good?
Bad?
Please tell me/other users what you thought [HERE](https://github.com/sasja-san/boom-hs/discussions).
User comments is the only way for me to know if this guide is being used or not.
:::