Mercurial > repos > other > SevenLanguagesInSevenWeeks
annotate 7-Haskell/README.txt @ 94:eb868f089bd1
Add notes and examples on data types and monads
Still to do: Day 3 exercises
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sun, 23 Jun 2019 20:13:55 +0100 |
parents | 7e4afb129bef |
children |
rev | line source |
---|---|
87 | 1 Install with `zypper install ghc` for the Glasgow Haskell Compiler. The interactive shell (REPL) is `ghci`. Currently on v8.6.5 (was 6.12.1 in the book). Haskell uses strong static types with inference. |
2 | |
3 Numbers behave like numbers. Strings have double-quotes and characters have single quotes. An array of characters (in square brackets) is a string. "+" is purely numeric addition - concatenation is "++". | |
4 | |
5 Equality is tested with "==" (equals) and "/=" (not equals - not "is divisible by"!). Haskell is strongly typed | |
6 | |
7 Indentation is significant in Haskell (like Python) BUT you can do an "if … then … else …" on a single line (however, it will assume that "if then" is a parse error, probably because functional code isn't imperative and so can't miss the else) | |
8 | |
9 Using functions on the wrong type of arguments seems to give helpful error messages along the lines of "No instance for (arg types) arising from a use of 'function' at X" (and "+" is a function). | |
10 ":t val" lets you see the type of a variable or value, and ":set +t" lets you see the type of returned values. | |
11 | |
12 In code, functions are defined as "type function_name param = body". In code, that is always prefixed with "let". | |
13 Functions return the result of their last instruction. | |
14 Full function definitions are preceded by a type definition line in the form "function_name :: type(s) -> type(s)". You can also have generics and have "function_name :: (Parent_class generic_name) => generic_name -> generic_name". | |
15 Code in files needs to be in a module, which starts "module ModuleName where" on the top line. | |
16 You can then do `:load filename.hs` in the console. This then puts you in that module on the CLI (the prompt changes). | |
17 | |
18 Haskell functions can use parameter matching on arguments - see factorial.hs. | |
19 | |
20 Invoking parameters doesn't use brackets (because they're used for tuples - except when it's just a single value, when they're ignored). | |
21 | |
22 Lists can be broken up with "head" and "tail" functions (or "fst" and "snd" for tuples) *or* they can be broken up on assignment. It's like Python multi-assignment, but with colon - "(_head:_tail)" (":" is the list construction operator). It can then be used in function definitions, e.g.: | |
23 size [] = 0 | |
24 size (h:t) = 1 + size t | |
25 | |
26 Haskell can create ranges with [start..end]. Specifying an invalid range (e.g. decreasing) gives an empty list. Specifying an increment is different to all other languages: [start, next..end] (e.g. "[10, 8..4]" gives "[10, 8, 6, 4]" and it works out to decrement by 2). | |
27 You can also do (lazy) infinite ranges by not specifying an end and then using "take" functions etc to pull just the values you need. | |
28 | |
29 List comprehension is slightly pythonic: "[expr | val <- [vals] ]" where "expr" is the expression to calculate in the new list, val is a variable used in expr and [vals] is the source list. It's effectively "expression for value in list". | |
30 You can also assign multiple independent variables, possibly from the same source. For example: | |
31 let crew = ["Kirk", "Spock", "McCoy"] | |
32 [(a,b) | a <- crew, b <- crew] | |
33 calculates all combinations of crew names. Changing it to: | |
34 [(a,b) | a <- crew, b <- crew, a /= b] | |
89
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
35 lets you add filtering to stop people being paired with themselves. Or you could use "a < b" to make it return unordered unique pairings. |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
36 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
37 Haskell has "map" - "map func list" - where "func" can be an anonymous function. |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
38 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
39 Anonymous functions are defined as: |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
40 (\param_1 … param_n -> function_body) |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
41 They can be called in-line, which looks odd: |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
42 (\x -> x) "Logical." |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
43 return "Logical." (because the anon function returns the value passed to it). |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
44 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
45 Alternatively, anonymous functions can be written as locally scoped functions after a main function definition, using "where": |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
46 squareAll list = map square list |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
47 where square x = x * x |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
48 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
49 Map can also be used with part of a function such as "(+ 1)". The book calls this a "section", but it isn't clear what it means. The wiki says a "section" is a partially applied infix operator (https://wiki.haskell.org/Section_of_an_infix_operator). |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
50 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
51 Haskell also has the standard functional filter (takes two parameter: a boolean "keep in list" function and a list) and foldl/foldr (take three parameters - a two-value function (value and accumulator), a starting value and a list). |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
52 You can even "foldl (+) 0 [1..3]" to sum by using "+" as the two-parameter folding function. |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
53 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
54 All of this has apparently been a lie, though. Haskell functions only have one argument! If you check the type of a multi-argument method then you get "arg_1_type -> arg_2_type -> arg_3_type -> result" rather than just "arg_1_type, arg_2_type, arg_3_type -> result" (Note: it won't be bracketed on the left because it's not expecting a three-tuple) |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
55 This world view can be made more apparent with the following: |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
56 let prod x y = x * y |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
57 let double = prod 2 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
58 let triple = prod 3 |
7e4afb129bef
Add initial Day 2 notes with functions, partially applied, and currying
IBBoard <dev@ibboard.co.uk>
parents:
87
diff
changeset
|
59 double and triple are partially applied functions, and the only (sane) way to be able to do that is if "prod x y" is a two-part function! The book says "When Haskell computes prod 2 4, it is really computing "(prod 2) 4". [insert anonymous functions]". This is called currying. |
94
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
60 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
61 Haskell has "data types" that use the 'data' keyword to specify an allowed set of values, and "types" that reference data types, e.g.: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
62 data Suit = Spades | Hearts |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
63 data Rank = Ten | Jack | Queen | King | Ace |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
64 type Card = (Rank, Suit) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
65 type Hand = [Card] |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
66 Suit can be Spades or Hearts, Rank can be Ten through Ace (no ordering implied here because we don't use "deriving Ord"), a Card is a tuple of a rank and a suit, and a Hand is a list of cards. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
67 This will fail to print values in the console unless we add "deriving (Show)" to the end of the "data …" lines. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
68 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
69 The book isn't clear what makes a "data" vs a "type", but it appears at first to be about whether it refers to values or other "classes" ('data' types). |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
70 Except that on the next page the "Triplet" definition doesn't meet that criteria. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
71 The Haskell wiki (https://wiki.haskell.org/Type) says types are defined using "data" and then "type" introduces synonyms. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
72 The examples are: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
73 data Maybe a = Just a | Nothing |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
74 (Define "Maybe" as a polymorphic of type variable "a" as "Just a thing of that type 'a'" or "Nothing") |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
75 data Tree a = Branch (Tree a) (Tree a) | Leaf a |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
76 (Define "Tree" as a polymorphic of type variable "a" as a Branch of two trees of type a or a Leaf of type a - you'd assume Branch and Leaf are defined elsewhere, but apparently this is enough to define them implicitly!) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
77 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
78 Note: The wiki defines Card as a "data", not as a "type". It also does it quite differently, using "Card = Card { value :: Rank, suit :: Suit } …", which seems recursive at first but is probably just like "class MyClass { public MyClass(var a) { … } }". |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
79 Also from the wiki, if you "deriving" something like Ord or Enum then you need to use "instance [derived thing] [my type] where … [implementation]" |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
80 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
81 Haskell also includes "classes", but they're not like Object-Oriented classes. They define which operations work on which inputs (e.g. Bool + Bool doesn't work, but Integer + Integer does). It's a bit more like an interface in that it defines functionality |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
82 and guarantees functions exist BUT with optional default implementation. For example, in the following definition: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
83 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
84 class Eq a where |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
85 (==), (/=) :: a -> a -> Bool |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
86 x /= y = not (x == y) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
87 x == y = not (x /= y) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
88 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
89 This means nothing on its own, but any data type "deriving Eq" will work with "==" and "/=" and you only need to implement one for your type to get the other for free. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
90 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
91 Monads. Monads are about function composition. They let you do the equivalent of "v = func1(v); v = func1(v); v = func2(v); return v" from imperative languages. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
92 You can do this pattern with a multi-line "let … in [return_value]" *but* you can't reuse variable names. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
93 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
94 Monads have a type constructor that takes a container, a function called "return" to wrap a function in the container, and a bind function called ">>=" that unwraps and chains the function. See monad.hs. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
95 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
96 A built-in use of monads is the "do" notation, which gives an imperative style on the surface but is implemented with monads: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
97 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
98 tryIo = do putStr "Enter your name: " ; |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
99 line <- getLine ; |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
100 let { backwards = reverse line } ; |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
101 return ("Hello. Your name backwards is " ++ backwards) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
102 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
103 That's a function call "tryIo" that uses "do" to look imperitive. This is just syntactic sugar so that you don't have to type ">>=" so often. The semi-colons are optional, and you can wrap the code in "{}" |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
104 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
105 A list is already defined as a monad using "instance". The definitions are simple enough: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
106 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
107 instance Monad [] where |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
108 m >>= f = concatMap f m |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
109 return x = [x] |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
110 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
111 So we define a monad on the list (top line) then define the binding, which applies the function to every value in the list, and then the return value is a list of the value. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
112 This allows lists to be used in "do …" blocks to write a "for each item in an array return a value" function, such as: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
113 let cartesian (xs, ys) = do x <- xs; y <- ys; return (x, y) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
114 Here, we take in two lists (xs and ys) and for each pair x and y we return the tuple BUT the monad definition and the "do" operation means that we get a list returned: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
115 Main> cartesian ([1..2], [3..4]) |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
116 [(1,3), (1,4), (2,3), (2,4)] |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
117 The book says this is "an alternate computational strategy" to list comprehension, but it reads almost entirely like list comprehension. The only difference in this example is that the "return" is at the end. c.f.: |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
118 let cartesian_list (xs, ys) = [(x,y) | x <- xs, y <- ys] |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
119 The password cracking example is a little more complex but could still probably be done with list comprehension. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
120 |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
121 "Maybe" is also a monad on the Maybe data type. The type is defined as "Nothing" or "Just a". The Monad returns is an alias for "Just" and so the result is "Just x" and there are two ways of binding the function. |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
122 Nothing >>= f = Nothing |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
123 (Just x) >>= f = f x |
eb868f089bd1
Add notes and examples on data types and monads
IBBoard <dev@ibboard.co.uk>
parents:
89
diff
changeset
|
124 i.e. there are no "null pointer exception"-type problems for chaining function calls as applying a function to Nothing safely returns Nothing. |