Lecture 6.0 — 2016-09-15

More on folds; Functor

This lecture is written in literate Haskell; you can download the raw source.

import Prelude hiding (unzip, tail)

We spent most of the class discussing ways to write folds, with a tiny bit on the Functor type class in the end.

We had two recipes for folds: a Java-based recipe for foldl and a Haskell-based recipe for foldr.

Figuring out the foldl

If you write some Java code like:

B myFunction(List<A> ls) {
  B acc = init;

  for (A a : ls) {
    acc = f(acc, a);
  }

  return acc;
}

then you have written a left fold. You can express myFunction(l) in Haskell as:

foldl f init l

Deriving a foldr

If you’ve written a “natural recursive” function by cases on lists, then you may have a foldr on your hands. If you write a function g:

g [] = init
g (x:xs) = f x (g xs)

then you have written a right fold. You can express g l as foldr f init l.

Eventually, it becomes natural to simply write the fold, without even thinking of the full definition of g. But before that happens, the equivalence here can serve as a recipe. Write the natural recursive definition of g and then convert it. For example:

unzip :: [(a,b)] -> ([a],[b])
unzip [] = ([],[])
unzip ((a,b):ps) = (a:fst (unzip ps),b:snd (unzip ps))

It’s pretty clear what our initial value is here, but the function f might be harder to see. We can refactor so that it looks more like the recipe:

unzip' :: [(a,b)] -> ([a],[b])
unzip' [] = ([],[])
unzip' ((a,b):ps) = f a b (unzip' ps)
  where f a b ls = (a:fst ls,b:snd ls)

It’s still not all the way, though: to really be the recipe, f should take two arguments.

unzip'' :: [(a,b)] -> ([a],[b])
unzip'' [] = ([],[])
unzip'' (p:ps) = f p (unzip'' ps)
  where f p ls = (fst p:fst ls,snd p:snd ls)

While we’re refactoring, we might as well clean up the code to use patterns rather than fst and snd:

unzip''' :: [(a,b)] -> ([a],[b])
unzip''' [] = ([],[])
unzip''' (p:ps) = f p (unzip''' ps)
  where f (a,b) (as,bs) = (a:as,b:bs)

At this point, it’s easy to convert to foldr:

unzip'''' :: [(a,b)] -> ([a],[b])
unzip'''' = foldr (\(a,b) (as,bs) -> (a:as,b:bs)) ([],[])

Extra state

Sometimes you need to keep track of more information while folding over a list. In class we used the following example:

everyOther :: [a] -> [a]
everyOther = snd . foldr (\x (keep,xs) -> (not keep,if keep then x:xs else xs)) (True,[])

It’s also common to define functions as compositions of higher-order functions. For example, we can define everyOther as a pipeline of some simple higher-order functions:

everyOther' l = map fst $
                filter (\(_,i) -> i `mod` 2 == length l `mod` 2) $
                zip l [1..length l]

Another example is the the function tail, normally defined as a simple O(1) operation with a deliberately non-exhaustive pattern match:

tail :: [a] -> [a]
tail (x:xs) = xs

If only to substantiate the claim that every function lists is a fold, we we can define it (much less efficiently):

tail' :: [a] -> [a]
tail' = snd . foldr (\x (xs1,xs2) -> (x:xs1,xs1)) ([],error "empty list")

(The function error is like undefined, but you get to write an error message. Do not use error for normal error conditions, but only for catastrophic, unrecoverable failures—like non-exhaustive pattern matches.)