-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path01_elem.qmd
398 lines (281 loc) · 11.1 KB
/
01_elem.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
---
title: 1. Babys First Haskell Functions
---
Let's start with looking at two versions of the same thing.
Hopefully this can be a bridge between what you know and the style Haskell uses: [*Hint:* You can click the numbers in the list below and have the corresponding code be highlighted.]{.aside}
```{.c}
int fact(int n); /* <1> */
int fact(int n) /* <2> */
{
if (n <= 1) /* <3> */
{ /* <3> */
return 1; /* <3> */
} /* <3> */
else /* <4> */
{ /* <4> */
return n * (fact(n-1)); /* <4> */
} /* <4> */
}
```
1. The function *declaration*. Tells the compiler "this function exists" which helps out with ordering of function calls. Without this `main()` will always have to be last in `main.c`
2. The function *definition* starts here. What the compiler uses to make assembly. First `int` is the return type, then comes the function name then the arguments. The `n` here is equivalent to what's to the left of the `=` in the Haskell function.
3. The base case.
4. A recursive call and a `return` statement. Because in C we differ between "statements" (things ending in `;` and some things using keywords like `for`) and "expressions" (all things arithmetic). `return (x + fact(x));` is a statement but `(x + fact(x))` is an expression.
```{.haskell}
fact :: Int -> Int -- <1>
fact 1 = 1 -- <2>
fact n = n * (fact (n-1)) -- <3>
```
1. This is the *type signature* of the function. It says that it takes an `Int` (the first mention) and returns (the second mention) an `Int`.
2. Base case, but only for the number `1`. To cover `0`, either add a similar line *or* solve it with guards. To see how guards work, look at the implementation of `takeWhile` in the list of exercise.
3. The n to the left of `=` is the argument to the function. Everything on the right side of `=` is the "return" expression.
Let's look at `elem`:
```{.haskell}
ghci> elem 'a' "baba ganoush"
True
ghci> elem 'x' "baba ganoush"
False
ghci> elem 7 [1..5]
False
ghci>
```
So what's the type of elem? Run `ghci> :t elem` and you should get this:
```{.haskell}
elem :: (Foldable t, Eq a) => a -> t a -> Bool
```
Small letters, especially the `a` correspond to the generic type, like `<T>` in languages like Java or C++.
In this particular case the generic type `t` (with a restriction `Foldable t`) represents a structure.
So the `t` could be replaced with `List` or `Set` or `BinarySearchTree` or many other types.
Let's simplify this by forcing the `Foldable` into a list. This gives us:
:::{.column-margin}
The Haskell function `elem` is similar to $\in$ from mathematics.
In maths you would write something like $\in : (a, \{a\}) \rightarrow \{T,F\}$ instead.
:::
```{.haskell}
elem :: (Eq a) => a -> [a] -> Bool
```
Now that's A LOT better. It says that the element `a` has to have the `Eq` trait/interface.^[It's written `class` in Haskell but spoken about as "typeclass".]
This means that the functions `==` and `/=` has to be defined for `a`.
If it does, then we can search for something of type `a` in a `[a]` ("list of a:s") and we will get back a `Bool`.
## Tutorial Structure / Your First Function
This tutorial will asks you to write your own version of existing library functions.
In this first instance you will write `myElem :: (Eq a) => a -> [a] -> Bool`.
So it has the same type as the library one, just `my` in front of it, to avoid naming conflicts.
:::{.column-margin}
<!-- quarto newlines fixes rendering -->
### Basic `ghci` usage.
| `$ ghci File.hs` to initially load a file.
|
| `ghci> :l File.hs` to load the file from within `ghci`.
|
| **DON'T MISS!!!**
| `ghci> :r` to reload the file last loaded.
|
| `ghci> myElem 'b' "abc"` An expression calling `myElem` to evaluate in `ghci`.
:::
Create a file `Exercise.hs` which looks like this:
```{.haskell}
import Data.List -- <1>
import Data.Char -- <2>
myElem :: (Eq a) => a -> [a] -> Bool -- <3>
myElem e [] = False -- <4>
myElem e (x:xs) = undefined -- <5>
```
1. Get all the list functions available in your terminal.
2. We will need some extra functions for `Char` manipulation.
3. Declaration of the type of the function `myElem`. All things of type `a` needs to be able to do `==`. It takes an `a`, a list of `a` and returns a Bool.
4. The "base case". When looking for the element `e` in an empty list, we won't find it. So we return False.
5. This is where you'll put your recursive call.
Now complete the function at the last line.
Haskell is quite squeamish about indentation, so you might get a few compilation errors before you get it right.
Here's an example of correct indentation for `if`, which also uses the `let-in` notation for local variable creation:
:::{.column-margin}
<!-- quarto newlines fixes rendering -->
|
|
|
|
### Writing Conventions
When making "alternate" version of existing values you usually stick a `'` at the end.
So in this case the string `s1` becomes `s1'`, pronounced as [s one prime] (swedish: [prim]).
The contents of `s1'` and `s2'` is the same as `s1` and `s2` except that any characters not in lower case has been turned into lower case.
:::
```{.haskell}
isCoolBeans :: String -> String -> String
isCoolBeans s1 s2 =
let s1' = map toLower s1 -- <1>
s2' = map toLower s2 -- <1>
in if (s1' == "cool") && (s2' == "beans") -- <2>
then "yay!" -- <3>
else "not so cool" -- <3>
```
1. You *could* place a `let` on each line declaring a new value, but convention is to just do it on the first line in your declaration block. This block gives you two new values, `s1'` [s one prime] and `s2'` who are available to you after the `in`.
2. The word `in` starts the function. When using `if` you don't need any special indentation.
3. The rule here is that `then` and `else` has to be indented **AND** they have to have the exact same level of indentation. Maybe the `then` clause contains a big let-in expression. That's perfectly fine, and that will have its own indentation rules. As long as the matching `else` is at the same level as `then`, it'll be fine.
```{.haskell}
ghci> isCoolBeans "COOL" "bEaNs"
"yay!"
```
:::{.column-margin}
A big trick in list function syntax is the `:` in the second argument to the function.
Read it as
> `(x:xs)` is a list whose first element is called `x` and whose tail (rest of the list) is called `xs`
:::
Let's now get back to `myElem`.
```{.haskell}
myElem e (x:xs) = if (e == x)
then True
else undefined
```
That's a bit better.
Now you check against the first element of the list.
But what if the call is `elem 'b' "abc"`, then you'll crash because you used `undefined`^[A magical placeholder word you should learn to use. It mostly means "I'll solve this later."].
Here's where you have to bring in recursion, so this is the complete function definition:
```{.haskell}
myElem :: (Eq a) => a -> [a] -> Bool
myElem e [] = False
myElem e (x:xs) = if (e == x)
then True
else myElem e xs
```
It's a bit verbose, so given that "or" is defined as this:
:::{.column-margin}
|
|
|
| The notation `_` for an argument means "We are not going to look at this argument when computing the result".
:::
```{.haskell}
(||) :: Bool -> Bool -> Bool
True || _ = True
False || b = b
```
Trying it out in `ghci`:
```{.haskell}
ghci> False || (False || False)
False
ghci> False || (False || True)
True
ghci> True || False
True
ghci> True || undefined
True
```
Let's use it for our `myElem` function!
```{.haskell}
myElem :: (Eq a) => a -> [a] -> Bool
myElem _ [] = False
myElem e (x:xs) = (e == x) || myElem e xs
```
## Your New Best Friend
If you've ever used Python you just put in your expression in the prompt and get the resulting value and side effects (such as file I/O & whatnot).
Haskell is extremely strict regarding its types.
When a Python program will run and crash because of a programmer error, a Haskell program refuses to load in the first place.
To look a bit at what's going on here, in `ghci` try using the `:t` command.
It asks for the type of an expression.
First ask about map:
```
ghci> :t map
```
The part `(a -> b)` means that the first argument is a function, taking something of type `a` and returning something of type `b`.
Now let's play with it a bit, try these in `ghci`:
```{.haskell}
ghci> :t toLower
ghci> :t toLower 'A'
ghci> toLower 'A'
ghci> :t map toLower
```
Because `toLower` has the type you saw (when running the above commands in `ghci`), those types gets bound up in `map`.
:::{.column-margin}
|
|
|
|
|
Using `:t` will be one of your primary tools for learning Haskell.
:::

## F**king Around With `:t` - Finding Out
First let's look at the function `length` and then write our own, just to get away from the `Foldable` nonsense.[You could use `ghci> :t +d length` and that will work to **d**efault the `Foldable` to a list. It will *often* work, but it's in no way a sure thing.]{.aside}
```{.haskell}
ghci> :t length
length :: (Foldable t) => t a -> Int
```
Now make your own:
:::{.panel-tabset}
## Behavior
```{.haskell}
ghci> :t myLength
myLength :: [a] -> Int
ghci> myLength []
0
ghci> myLength ""
0
ghci> myLength [1..5]
5
ghci> myLength "abc123"
6
```
## Implementation
```{.haskell}
myLength :: [a] -> Int
myLength [] = 0
myLength (_:xs) = 1 + myLength xs
```
:::
And also make one that enforces the `String` type.
:::{.panel-tabset}
## Behavior
```{.haskell}
ghci> :t myStringLength
myLength :: String -> Int
ghci> myStringLength ""
0
ghci> myStringLength "1234"
4
ghci> myStringLength [1..5]
<interactive>:76:17: error:
• No instance for (Num Char) arising from the literal ‘1’
• In the expression: 1
In the first argument of ‘myStringLength’, namely ‘[1 .. 4]’
In the expression: myStringLength [1 .. 4]
```
## Implementation
```{.haskell}
myStringLength :: String -> Int
myStringLength cs = myLength cs
```
:::
Let's look at `map` again, which in many cases acts as your `foreach` function.
```{.haskell}
ghci> :t map
map :: (a -> b) -> [a] -> [b]
```
Now, **BEFORE** asking `ghci` about the type of the below expression, can you figure out what it will be?
It's a matter of search-and-replace with the types that comes from the first argument to `map`.
:::{.panel-tabset}
## Query
```{.haskell}
ghci> :t map myStringLength
```
## Answer
```{.haskell}
map myStringLength :: [String] -> [Int]
```
:::
You could write a list of strings manually, which is kind of annoying:
```{.haskell}
["my", "list", "of", "strings"]
```
Instead, try using the function `words`.
```{.haskell}
ghci> :t words
words :: String -> [String]
ghci> words "hello everyone reading"
["hello","everyone","reading"]
```
Now combine `map myStringLength` with `words` into something computable.
You will need to put parenthenses at the right place for this to work.
---
Now you have the most basic tools for learning Haskell.
# [To the Exercise $\rightarrow$](02_my_funs.qmd)