Lecture 2 (2018-01-18)
Higher-order functions
We spent the class discussion list functions, using anonymous
functions (lambdas, written `\x1 x2 x3 ... -> e`), with a focus on
maps and folds. I meant to discuss graphs but didn't get to it---I've
got some notes at the bottom anyway.
Before I go into detail about folds, you should know how to turn on warnings in
GHC:
> {-# OPTIONS_GHC -Wall -fno-warn-unused-imports #-}
I turn off the unused imports warning, because it's annoying and not
really relevant for us. For this file, we'll also turn off warnings
about name shadowing.
> {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
In general, though, it's better to leave that warning on.
> module Lec02 where
>
> import Prelude hiding (map, foldr, foldl) -- hide some things we'll define ourselves
>
> import Data.Map (Map)
> import qualified Data.Map as Map
> import Data.Set (Set)
> import qualified Data.Set as Set
## Maps

We started by observing some patterns:
> addOne :: [Int] -> [Int]
> addOne [] = []
> addOne (x:xs) = x+1 : addOne xs
>
> double :: [Int] -> [Int]
> double [] = []
> double (x:xs) = 2*x : double xs
>
> negateAll :: [Bool] -> [Bool]
> negateAll [] = []
> negateAll (b:bs) = not b : negateAll bs
And abstracting out a general framework:
> map :: (a -> b) -> [a] -> [b]
> map _ [] = []
> map f (x:xs) = f x : map f xs
The `map` function is a *higher-order function*, because it's a
function that takes another function as its input. We can understand
its behavior diagrammatically like so:
```
: :
/ \ / \
1 : f 1 :
/ \ => / \
2 : f 2 :
/ \ / \
3 [] f 3 []
```
Higher-order functions are extremely powerful: they let us express
whole patterns of computation in a single fell swoop. Once we've
written `map`, we don't need to ever write that kind of recursion
manually again. Not only do we avoid silly errors in writing map-like
functions, we also save time.
## Folds

There's some nice [material on Wikipedia about
folds](https://en.wikipedia.org/wiki/Fold_(higher-order_function)).
There are two kinds of folds: rightwards folds and leftwards
folds. They correspond to direct and accumulating recursion,
respectively. Since direct is easier to understand, we looked at foldr
first.
The folds we'll define---`foldr` and `foldl` are already in the Prelude...
so we can hide them for the rest of this file.
We'll need a few more imports below.
**foldr**
Here's a rightward fold, a higher-order function that seems a bit
opaque at first blush.
> foldr :: (a -> b -> b) -> b -> [a] -> b
> foldr _ b [] = b
> foldr f b (a:as) = f a (foldr f b as)
We can understand`foldr` using
the following equation:
```
foldr f b [a1,...,an] = f a1 (f a2 (... (f an b) ...))
```
We can also understand `foldr` as replacing a list's 'spine' with
function applications, like so:
```
: f
/ \ / \
1 : 1 f
/ \ => / \
2 : 2 f
/ \ / \
3 [] 3 b
```
We defined a few different functions using `foldr`.
> head :: [a] -> a
> head = foldr (\a _ -> a) undefined
>
> product :: [Int] -> Int
> product = foldr (*) 1
>
> and, or :: [Bool] -> Bool
> and = foldr (&&) True
> or = foldr (||) False
In general, a function defined like:
```haskell
g [] = v
g (x:xs) = f x (g xs)
```
can be implemented using `foldr` as:
```haskell
g = foldr f v
```
**foldl**
Leftward folds are like rightward folds but using a different pattern:
```
: f
/ \ / \
1 : f 3
/ \ => / \
2 : f 2
/ \ / \
3 [] b 1
```
The code for a leftward fold looks more like the *accumulator passing
style* functions we've already written.
> foldl :: (b -> a -> b) -> b -> [a] -> b
> foldl _ b [] = b
> foldl f b (a:as) = foldl f (f b a) as
Correspondingly, `foldl` satisfies the following equation:
```
foldl f b [a1,...,an] = f (... (f (f b a1) a2) ...) an
```
We can use `foldl` to define the list reversal:
> rev :: [a] -> [a]
> rev = foldl (\acc x -> x:acc) []
We can also use it to easily define `last`:
```haskell
last = foldl (\a b->b) undefined
```
Finally, we can mechanically translate recursive accumulating functions defined like:
```haskell
g xs = g' xs b
g' [] acc = acc
g' (x:xs) acc = g' xs (f x acc)
```
into leftward folds like:
```haskell
g' = foldl f b
```
## Graphs

We didn't get a chance to apply our newfound prowess with maps and
folds to working with maps and sets.
Haskell's maps and set data types (in `Data.Map` and `Data.Set`,
respectively) are *opaque*: we don't get to know what constructors
they have, so we have no choice but to use maps, folds, and other
higher-order functions to work with them.
> type Node = String
> type Graph = Map Node (Set Node)
Note how we (a) simulataneously assign types to a number of values
while (b) simultaneously *defining* those values.
> a,b,c,d,e :: Node
> [a,b,c,d,e] = ["a","b","c","d","e"]
You can also use tuples, as in:
> f,g :: Node
> (f,g) = ("f","g")
Next, we can define two graphs.
> g1,g2 :: Graph
> g1 = Map.fromList [(a, Set.fromList [b,c]),
> (b, Set.fromList [a,d]),
> (c, Set.fromList [a,d]),
> (d, Set.fromList [b,c])]
> g2 = Map.fromList [(a, Set.fromList [b,c]),
> (b, Set.fromList [a,d]),
> (c, Set.fromList [a,d]),
> (d, Set.fromList [])]
These correspond to the following directed graphs:
```
a ↔ b
↕ ↕ g1
c ↔ d
a ↔ b
↕ ↓ g2
c → d
```
We can easily calculate the out-degree of each node:
> outDegrees :: Graph -> Map Node Int
> outDegrees = Map.map Set.size
And we can just as easily check that all nodes have out-degree one or
more:
> allDegreeOneOrMore :: Graph -> Bool
> allDegreeOneOrMore = Map.foldr ((&&) . (>=1)) True . outDegrees
We can also write a function that takes a node and a graph and makes a
new graph where every node has out-degree of at least one.
> makeDegreeOne :: Node -> Graph -> Graph
> makeDegreeOne n g = Map.insert n (Set.singleton n) g'
> where g' = Map.map connect g
> connect tgts =
> if Set.null tgts
> then Set.singleton n
> else tgts