Lecture 3.0 — 2016-09-06
Higher-order functions; datatypes; our first interpreter
This lecture is written in literate Haskell; you can download the raw source.
The first half of class focused on understanding datatypes, anonymous functions (lambdas, written \x1 x2 x3 ... -> e
), followed by maps and folds. We concluded by discussing graphs.
Before I go into detail about folds, we saw how to turn on warnings in GHC:
{-# OPTIONS_GHC -Wall -fno-warn-unused-imports #-}
We 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.
Folds
There’s some nice material on Wikipedia about folds.
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…
import Prelude hiding (foldr, foldl)
so we can hide them for the rest of this file.
We’ll need a few more imports below.
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Set (Set)
import qualified Data.Set as Set
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 understandfoldr
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:
g [] = v
g (x:xs) = f x (g xs)
can be implemented using foldr
as:
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 accumulating version of insertion sort:
insert :: [Int] -> Int -> [Int]
insert [] x = [x]
insert (y:ys) x | x < y = x:y:ys
| otherwise = y:insert ys x
insertionSort :: [Int] -> [Int]
insertionSort = foldl insert []
We can also use it to easily define last
:
last = foldl (\a b->b) undefined
Finally, we can mechanically translate recursive accumulating functions defined like:
g [] acc = acc
g (x:xs) = g xs (f x acc)
into leftward folds like:
g = foldl f acc
Graphs
In the last twenty minutes or so of class, we talked about how to use these higher-order functions to work with maps and sets. When dealing with Data.Map
or Data.Set
, we don’t have any constructors to pattern match on… so we must either use higher-order functions or convert our maps and sets to lists. Converting to a list can be annoying (not to mention less efficient), so I strongly prefer working with maps and sets as is.
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 [])]
We can easily calculate the degree of each node:
degrees :: Graph -> Map Node Int
degrees = Map.map Set.size
And we can just as easily check that all nodes have degree one or more:
allDegreeOneOrMore :: Graph -> Bool
allDegreeOneOrMore = Map.foldr ((&&) . (>=1)) True . degrees
We can also write a function that takes a node and a graph and makes a new graph where every node has at least degree 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