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
|
# Higher-order programming and type inference
## Anonymous functions
**Anonymous functions**, or **lambda abstractions** ("lambdas" for short), are, as the name implies, functions without a name. They are useful when an expression requires a *little inline function* and giving a full definition with a name to that function, only to use it in just one place, would be too much.
Lambda abstractions start with a backslash "\", followed by one or more arguments, and a right arrow which marks the beginning of the function's expression. They are usually enclosed in round parentheses, mostly due to precedence of evaluation in expressions. The backslash is supposed to look like part of the lowercase lambda greek character.
-- Instead of:
gt100 :: Integer -> Bool
gt100 x = x > 100
greaterThan100 :: [Integer] -> [Integer]
greaterThan100 xs = filter gt100 xs
-- We can simply write:
greaterThan100 :: [Integer] -> [Integer]
greaterThan100 xs = filter (\x -> x > 100) xs
-- Or even better:
greaterThan100 :: [Integer] -> [Integer]
greaterThan100 xs = filter (>100) xs
In the last function definition, `greaterThan100` uses `(>100)` which is called an **operator section**. Operator sections are equivalent to lambda abstractions, but they are shorter as they hide some details of the function. Operator sections allow us to **partially apply** an operator to one of its two arguments.
Given an operator `?`:
- `(?y)` means `\x -> x ? y`
- `(y?)` means `\x -> y ? x`
Lambda abstractions can be used in *any place* where a function is required. This means that we can *define and call a lambda abstraction at the same time*. For example, we can write in GHCi:
Prelude> (\x y z -> [x, 2*y, 3*z]) 5 6 3
[5, 12, 9]
And it works with operator sections as well:
Prelude> (>100) 102
True
Prelude> (100>) 102
False
Prelude> map (*6) [1..5]
[6,12,18,24,30]
## Function composition
**Function composition** is a property of function that allow us to express the application of multiple successive functions as a single one, where the next takes as input the value that the previous returned as output.
Let's say we have a function `f` whose type is `a -> b` and another function `g` with type `b -> c`, where each letter is a separate type variable (as we've seen when we talked about Polymorphism in lecture 3). Given a value of type `a`, we can apply `f` first and then `g`. We can do this because the first function application, `f a`, gives us a value of type `b`, which is the same type the function `g` needs in order to return a value of type `c`. For this reason, the expression `g (f a)` is a valid expression in Haskell.
f :: a -> b
f a = ...
g :: b -> c
g b = ...
v1 :: a
v1 = ...
v2 :: c
v2 = g (f a)
However, as it happens in math, we can create a new function `h` that applies both `f` and `g` in a single step. `h` is called the *function composition* of `f` and `g`. In Haskell, we can compose functions using the dot `.` operator:
h :: a -> c
h = g . f -- same as: h a = (g . f) a
Function composition is useful for writing concise and elegant code as it allows us to compose more complex functions starting with simpler ones. It also fits well in the "wholemeal" style. However, it could be a little counter-intuitive at first, since functions are evaluated from right to left, instead of the order we usually read in, which is from left to right.
## Currying and partial application
There is one hidden truth in Haskell: *all functions only take one argument*, even those which clearly accept more than one argument. This is the reason why all function types have "extra" arrows. Let's examine this function:
f :: Int -> Int -> Int
f x y = 2*x + y
At first, judging by all we've learned so far, we'd say that `f` takes two arguments (two `Int`s) and returns one value (also an `Int`). However, this is actually not true. `f` *takes one argument* (`Int`) and *returns a function* (`Int -> Int`). Then, the returned function takes *another* argument (`Int`) and returns the final result (`Int`). This function is equivalent to the previous:
f' :: Int -> (Int -> Int)
f' x y = 2*x + y
Function arrows associate to the right (they are **right-associative**), so `W -> X -> Y -> Z` is equivalent to `W -> (X -> (Y -> Z))`. On the other hand, function application is **left-associative** and `f 3 2` is just a shorthand for `(f 3) 2`. Since it's left-associative, we get to have the nice notation `f 3 2`, which almost looks like as if `f` accepted multiple arguments.
When we write `f 3 2` or, equivalently but more evidently, `(f 3) 2`, what Haskell really does is it breaks `f` into two smaller functions: one that feeds 3 to `f`, replacing all the occurrences of the first argument with 3, and another one, returned from the former, with expression `6 + y`, which is then applied to the second argument.
And all this works for lambda abstractions with multiple arguments, too! The following two lines do the exact same thing, but one is syntax sugar for the other.
\x y z -> ...
\x -> (\y -> (\z -> ...))
And, in the same way, `f x y z = ...` is just syntax sugar for `f = \x -> (\y -> (\z -> ...))`.
This is called **currying**: the decomposition of a function with N arguments into N functions of a single argument, each applied to a single argument and returning another function, and eventually returning the final value. This technique gets its name from the British mathematician Haskell Curry, although the idea was previously discovered by Moses Schönfinkel.
We can build functions which actually take two arguments with a tuple as its first argument, although it still requires a single argument which happens to be a tuple.
f'' :: (Int,Int) -> Int
f'' (x,y) = 2*x + y
The standard library defines two functions called `curry` and `uncurry` which convert between the two representations of a function with two arguments. `uncurry` can be useful:
Prelude> uncurry (+) (2,3)
5
### Partial application
We actually already explained what a *partial application* is, when we taked about currying. We just didn't give it a name yet!
A **partial application** of a function with multiple arguments (functions in Haskell actually don't have multiple arguments, etc.) is the application of a function to just a subset of its first arguments. In other words, a partially applied function is a function which does not return a value, because not all its arguments have been provided.
Sadly, Haskell does not let us decide which arguments to apply. We need to apply them strictly in order from the first to the last, without missing any of them. There is a good reason behind this behavior, which is, agin, currying. Since we apply and return functions from the first to the last (left-associativity), we *need* to apply all `n-1` arguments before getting the `n`th "curried" function. Infix operators are an exception, as we've seen with operator sections.
For this reason, it's very important to choose the order of the arguments of our functions! They must be **ordered from least to greatest variation**. In other words, the arguments which are often the same should go first, and those which often change should go last, in order.
### Wholemeal programming
Let's consider the following function, `foobar`.
foobar :: [Integer] -> Integer
foobar [] = 0
foobar (x:xs)
| x > 3 = (7*x + 2) + foobar xs
| otherwise = foobar xs
Although this looks pretty straightforward for what we've done so far, it's not idiomatic at all. For the Haskell style, this function both does too much at once and works at a level that's too low. It's not wholemeal programming at all.
Instead of thinking in terms of *how* it's done, we need to think in terms of *what* is done, and this means thinking about our function as a series of incremental transformations in order to turn the entire input into the desired output.
foobar' :: [Integer] -> Integer
foobar' = sum . map (\x -> 7*x + 2) . filter (>3)
`foobar'` is how `foobar` should have been from the beginning. It's much more concise and clean, but it's not only about that. The thing is: we can clearly see a pipeline of functions, from right to left, that operate on the input sequentially.
The `filter` function filters out all numbers smaller or equal to 3, thus getting rid of the `otherwise` branch. Then, `map` calculates the expression and finally `sum` gets rid of the recursive calls.
In `foobar'`, `map` and `filter` have been partially applied. The type of `filter` would normally be `(a -> Bool) -> [a] -> [a]` but, by applying the operator section `(>3)`, whose type is `Integer -> Bool`, we applied the principle of currying to `filter` and ended up with a function with type `[Integer] -> [Integer]`, which is something that composes very well with `map`.
The style shown in `foobar'`, which does not reference its arguments and defines what the function *is* rather than what it *does*, is called **point-free style**. It's beautiful although, taken too far, can lead to extremely confusing results.
## Folds
Last time we talked about recursion patterns (that is, lecture 3) we left folds for another time. Now, the time has come.
**Fold** is a pattern that performs an operation on all elements of a list together, to form a final answer that not a list.
The fold pattern's definition is not really straightforward, but folds are actually pretty common! Summing all elements of a list together is a fold, as is the product of them or the length of the list. Of the three recursion paterns we've seen so far, that are, map, filter, and fold, fold is probably the trickiest.
Let's examine the typical implementation of a fold.
fold :: b -> (a -> b -> b) -> [a] -> b
fold z f [] = z
fold z f (x:xs) = f x (fold z f xs)
So, `fold` accepts: a value of type `b`, a function of type `a -> b -> b`, and a list with elements of type `a`. With those, it returns a value of type `b`. With its arguments, `fold` applies `f` to the current and next element, starting from the first, and uses `z` as last element when no other is left.
With `sum` and `product` the function doesn't need to have distinct types for its two arguments, so why's that? Because for `length` it does need them! The output of `length` is a number, regardless of whatever the list elements' type is. From that, we can conclude that there are at least two types involved here: one for the elements of the list, and another one for the returned value. The function provided to `fold` just needs to do something with an element of the list and our value.
The Prelude already provides a function that folds, in particular two: `foldr` and `foldl`. `foldr` folds from the right, that is, what we've seen in our implementation. `foldl` folds from the left, so it uses `z` immediately with the first element of the list, then continues to apply `f` until the last element.
|