-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathLecture2.lhs
359 lines (232 loc) · 10.2 KB
/
Lecture2.lhs
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
---
number: 2
title: Intro to Haskell
published: 2021-09-10
exercises: https://ca.prairielearn.com/pl/course_instance/2333/assessment/16845
---
> module Lecture2 where
(Note: no strange imports this time. Plain old `ghci` should load this file just fine.)
Playing With Some Building Blocks
=================================
As with most languages, Haskell lets us give names to values:
> x = 42
> y = 'A'
> oppositeOfTrue = False
Let's play with these in `ghci`:
```haskell
y
:type y
```
Check the other variables' types and values as well.
(*Exercise!*)
Let's explore one more type:
> s = "Hello World!"
> doubleS = s ++ s
Strings in Haskell are lists of characters (written `[Char]`).[^strings]
Definition or Assignment?
-------------------------
Remember that a Haskell program is a big expression,
*not* a series of instructions (statements) to execute.
In Java `x = 5` is assignment, meaning "put the new
value `5` into `x`".
In Haskell, `x = 5` is definition: define `x` to be `5`.
So, what if we give a new value to `x`?
Try uncommenting this (remove the `{-` and `-}`) and
loading (or reloading: `:reload`) the file:[^ghci]
> {- x = 525600 -}
Why did this happen? (*Exercise!*)
Values or Expressions
---------------------
(We will probably skip this part in lecture. If so, and if you have trouble
finding expressions that cause errors, consider trying the built-in functions
`div` or `head`.)
Now, in Haskell, do we give names to expressions or to values? Let's try these:
> n = 10 + 2
> m = 25 `div` 3 -- equivalent to div 25 3
> o = 25 / 3
Does it matter? What's the difference?
Take a few minutes to define some expressions, show their values, and show their types.
Try specifically to **define an expression that causes no error but
then when we evaluate we get an error**.
(*Exercise!*)
Type Inference
--------------
Haskell figures out the types of your expressions. (It performs
_type inference_.) We'll explore the details of that later, with
connections to "unification".
For now, think of it as detective work: Investigate each element of an
expression for its type "constraints": what do you know for sure about its type?
Puzzle together those constraints until you know the overall type.
(There's a challenging, *very* quiz-like exercise on this,
which is for out-of-class. Try it then, and ask questions on Piazza!)
Building Up Functions
=====================
We've used a handful of built-in arithmetic functions, and you'll see
more in the reading.
Let's define a few of our own simple functions.
Define a function that adds one to an `Int`
-------------------------------------------
> add1 :: Int -> Int
> add1 n = n + 1
There's actually a built-in function that does the same. We could also
just put *its* value into our function:
> add1' :: Int -> Int
> add1' = succ
Finding the `n`th Odd Number
----------------------------
**Monday 13 Sep 2021: Stopped here in class. Left draft of `nthOdd` as an exercise.**
Now, define a function that produces the `n`th odd number:
> -- >>> nthOdd 1
> -- 1
> --
> -- >>> nthOdd 2
> -- 3
> nthOdd :: Int -> Int
> nthOdd n = 2*n - 1
(*Exercise!*)
(Hint: if you double a number `n`, it gives you the `n`th even number.)
Define an exactly-one-true function
-----------------------------------
Define a function that determines if **exactly one**
of three Boolean values is true. You'll want to use
the `&&` (and), `||` (or), and `not` functions.
> oneTrue :: Bool -> Bool -> Bool -> Bool
> oneTrue b1 b2 b3 = (b1 && not b2 && not b3) ||
> (not b1 && b2 && not b3) ||
> (not b1 && not b2 && b3)
Building Farther Using Cases
----------------------------
One of the central mechanisms Haskell uses to make decisions
and break down data is _pattern-matching by cases_.[^cases]
In fact, built-in functions like `head` and `tail` that break up data structures
are implemented in terms of pattern-matching:
> myHead :: [a] -> a
> myHead (x:_) = x
> myTail :: [a] -> [a]
> myTail (_:xs) = xs
`(_:_)` matches a non-empty list. `(x:_)` does the same, but defines
`x`'s value to be the head of the list.
([Guards](https://en.wikibooks.org/wiki/Haskell/Control_structures#if_and_guards_revisited)
are handy also. Learn about those from the readings!)
We can use any type of data in our cases, like bools:
> myNot True = False
> myNot False = True
Now, redefine `oneTrue` (as `oneTrue'`) except by cases instead:
> oneTrue' :: Bool -> Bool -> Bool -> Bool
> oneTrue' True False False = True
> oneTrue' False True False = True
> oneTrue' False False True = True
> oneTrue' _ _ _ = False
> secondElt (_:x:_) = x
Exercise
--------
Now, we'll try an exercise where we interpret lists of `Bool`s
as if they were single `Bool` values. Go try it out as an *exercise!*
It may help to know that you can use patterns like:
+ `[]`: the empty list
+ `(x:xs)`: a _non-empty_ list with the head `x` and tail `xs`,
+ `[True, False]`: a length-two list with `True` as its first value, and `False`
as its second.
Lazy Evaluation, Referential Transparency, and Control Structures
=================================================================
We've mentioned before that Haskell uses "lazy evaluation", meaning loosely that it avoids
evaluating expressions until forced to.
Let's use that to define **our own `if` expression**:
> myIf :: Bool -> a -> a -> a
> myIf True thenArg _ = thenArg
> myIf False _ elseArg = elseArg
Would this work in Java?
```java
public static int myIf(bool condition, int thenArg, int elseArg) {
if (condition)
return thenArg;
else
return elseArg;
}
myIf(a != 0, b / a, 0);
```
Let's try it in Haskell!
> a = 0
> b = 3
> result = myIf (a /= 0) (b `div` a) 0
Now type `result` in `ghci`.
Referential Transparency and No Side Effects
--------------------------------------------
But wait. If we don't even know when an expression will be evaluated...
if expressions can be evaluated "out of order" with the way we expect
them to go... then what happens with code like `x++`?[^parallelism]
`x++` could change `x`'s value at some unpredictable time in
a Haskell program. Or it could *never* change `x`'s value, if it happened
never to get evaluated. How can we possibly increment a variable's value
given all that?
Haskell's answer: We can't. Haskell *disallows side-effects*: effects your code has
besides computing a value, like changing the value of a variable.[^no-side-effects]
That means Haskell also offers something called **referential transparency**:
once you know an expression's value[^context], you know
that the value and expression mean the same thing. So:
+ you can freely substitute the value for the expression
+ you can evaluate the same expression again, and you *will* get the same result
+ you can substitute in a _different_ expression if it also has the same value
That's tremendously handy for reasoning about your programs (like when
you're testing, for example!).
Recursive Functions
===================
We'll use recursion frequently in defining our functions (at least at first!).
Fortunately, most recursive functions we create do just what Haskell is good at:
break down processing of data into cases based on the structure of the data, and
then define the result of each case based on the data from those structures.
So, let's write a couple of our own recursive functions. First, we'll double
each element in a list. Let's break it into cases, and then figure out the cases:
> -- If we doubleAll an empty list, that's still just an empty list.
> -- If we doubleAll on a list with x at the head and xs at the tail,
> -- we should get 2*x as the head and
> -- the result of doubleAll on xs as the tail.
> doubleAll :: [Int] -> [Int]
> doubleAll [] = []
> doubleAll (x:xs) = 2*x : doubleAll xs
Now, let's try to intersperse a new letter between each pair of letters in a string.
For example, `intersperse c [letter1, letter2, letter3]` is
`[letter1, c, letter2, c, letter3]`.
> -- >>> intersperse 'o' "www"
> -- "wowow"
>
> -- >>> intersperse 'x' ""
> -- ""
>
> -- >>> intersperse 'y' "p"
> -- "p"
>
> -- >>> intersperse 'y' "ab"
> -- "ayb"
>
> -- >>> intersperse 'z' "ab"
> -- "azb"
>
> intersperse :: Char -> [Char] -> [Char]
> intersperse _ [] = []
> intersperse _ [c] = [c]
> intersperse i (c1:c2:cs) = c1:i:intersperse i (c2:cs)
Over in the *exercises!* we have a mystery recursive function for you to evaluate
and a recursive function for you to define.
[^strings]: There's actually a more robust text type available and GHC support for "polymorphic" strings,
in much the way that the number `5` came out with the type `Num p => p`, meaning "some type `p`,
where `p` is an instance of the `Num` type class, i.e., is numeric".
[^ghci]: You'll get a different result if you run `x = 525600` at the REPL prompt in `ghci`, which exposes
some interesting techniques used to make the REPL work!
[^cases]: In fact, `ghc` compiles Haskell to a Haskell-lite intermediate language called Core,
which lacks `if` expressions and even cases in function definitions and boils them all down
to an explicit `case` expression construct that uses pattern-matching by cases.
[^no-side-effects]: In fact, with a very small number of "functions"
you are strongly encouraged never to use, it is possible to cause side
effects. Doing so is a mess in a Haskell program for the reasons we talked about!
There are also some side-effects you can't disallow in the real world.
For example, how long a piece of code takes to run on your computer. On the other hand,
Haskell has *brilliant* solutions for side effects like reading input and displaying output
that do *not* violate the no-side-effects rule within your program!
[^parallelism]: "We don't even know when an expression will be evaluated. Expressions might be
evaluated out of order." Does that sound a bit like the hazards of parallelism and concurrency
to you? I wonder if strictly-functional programming is **hugely** valuable in modern programming
because of the growing importance of parallelism and concurrency.
[^context]: Within a particular context, that is. For example `x + 1` in your program
may be very different from in mine, and `x + 1` in one call to a function with `x` as
a parameter may be very different from in a different call to that function.