Question

I had to implement the haskell map function to work with church lists which are defined as following:

type Churchlist t u = (t->u->u)->u->u

In lambda calculus, lists are encoded as following:

[] := λc. λn. n
[1,2,3] := λc. λn. c 1 (c 2 (c 3 n))

The sample solution of this exercise is:

mapChurch :: (t->s) -> (Churchlist t u) -> (Churchlist s u)
mapChurch f l = \c n -> l (c.f) n

I have NO idea how this solution works and I don't know how to create such a function. I have already experience with lambda calculus and church numerals, but this exercise has been a big headache for me and I have to be able to understand and solve such problems for my exam next week. Could someone please give me a good source where I could learn to solve such problems or give me a little guidance on how it works?

Was it helpful?

Solution

All lambda calculus data structures are, well, functions, because that's all there is in the lambda calculus. That means that the representation for a boolean, tuple, list, number, or anything, has to be some function that represents the active behavior of that thing.

For lists, it is a "fold". Immutable singly-linked lists are usually defined List a = Cons a (List a) | Nil, meaning the only ways you can construct a list is either Nil or Cons anElement anotherList. If you write it out in lisp-style, where c is Cons and n is Nil, then the list [1,2,3] looks like this:

(c 1 (c 2 (c 3 n)))

When you perform a fold over a list, you simply provide your own "Cons" and "Nil" to replace the list ones. In Haskell, the library function for this is foldr

foldr :: (a -> b -> b) -> b -> [a] -> b

Look familiar? Take out the [a] and you have the exact same type as Churchlist a b. Like I said, church encoding represents lists as their folding function.


So the example defines map. Notice how l is used as a function: it is the function that folds over some list, after all. \c n -> l (c.f) n basically says "replace every c with c . f and every n with n".

(c 1 (c 2 (c 3 n)))
-- replace `c` with `(c . f)`, and `n` with `n`
((c . f) 1 ((c . f) 2) ((c . f) 3 n)))
-- simplify `(foo . bar) baz` to `foo (bar baz)`
(c (f 1) (c (f 2) (c (f 3) n))

It should be apparent now that this is indeed a mapping function, because it looks just like the original, except 1 turned into (f 1), 2 to (f 2), and 3 to (f 3).

OTHER TIPS

So let's start by encoding the two list constructors, using your example as reference:

[] := λc. λn. n
[1,2,3] := λc. λn. c 1 (c 2 (c 3 n))

[] is the end of list constructor, and we can lift that straight from the example. [] already has meaning in haskell, so let's call ours nil:

nil = \c n -> n

The other constructor we need takes an element and an existing list, and creates a new list. Canonically, this is called cons, with the definition:

cons x xs = \c n -> c x (xs c n)

We can check that this is consistent with the example above, since

cons 1 (cons 2 (cons 3 nil))) =
cons 1 (cons 2 (cons 3 (\c n -> n)) = 
cons 1 (cons 2 (\c n -> c 3 ((\c' n' -> n') c n))) =
cons 1 (cons 2 (\c n -> c 3 n)) =
cons 1 (\c n -> c 2 ((\c' n' -> c' 3 n') c n) ) =
cons 1 (\c n -> c 2 (c 3 n)) =
\c n -> c 1 ((\c' n' -> c' 2 (c' 3 n')) c n) =
\c n -> c 1 (c 2 (c 3 n)) =

Now, consider the purpose of the map function - it is to apply the given function to each element of the list. So let's see how that works for each of the constructors.

nil has no elements, so mapChurch f nil should just be nil:

mapChurch f nil
= \c n -> nil (c.f) n
= \c n -> (\c' n' -> n') (c.f) n
= \c n -> n
= nil

cons has an element and a rest of list, so, in order for mapChurch f to work propery, it must apply f to the element and mapChurch f to rest of the list. That is, mapChurch f (cons x xs) should be the same as cons (f x) (mapChurch f xs).

mapChurch f (cons x xs)
= \c n -> (cons x xs) (c.f) n
= \c n -> (\c' n' -> c' x (xs c' n')) (c.f) n
= \c n -> (c.f) x (xs (c.f) n)
-- (c.f) x = c (f x) by definition of (.)
= \c n -> c (f x) (xs (c.f) n)
= \c n -> c (f x) ((\c' n' -> xs (c'.f) n') c n)
= \c n -> c (f x) ((mapChurch f xs) c n)
= cons (f x) (mapChurch f xs)

So since all lists are made from those two constructors, and mapChurch works on both of them as expected, mapChurch must work as expected on all lists.

Well, we can comment the Churchlist type this way to clarify it:

-- Tell me...
type Churchlist t u = (t -> u -> u) -- ...how to handle a pair
                    -> u            -- ...and how to handle an empty list
                    -> u            -- ...and then I'll transform a list into 
                                    -- the type you want

Note that this is intimately related to the foldr function:

foldr :: (t -> u -> u) -> u -> [t] -> u
foldr k z [] = z
foldr k z (x:xs) = k x (foldr k z xs)

foldr is a very general function that can implement all sorts of other list functions. A trivial example that will help you is implementing a list copy with foldr:

copyList :: [t] -> [t]
copyList xs = foldr (:) [] xs

Using the commented type above, foldr (:) [] means this: "if you see an empty list return the empty list, and if you see a pair return head:tailResult."

Using Churchlist, you can easily write the counterpart this way:

-- Note that the definitions of nil and cons mirror the two foldr equations!
nil :: Churchlist t u
nil = \k z -> z

cons :: t -> Churchlist t u -> Churchlist t u
cons x xs = \k z -> k x (xs k z)  

copyChurchlist :: ChurchList t u -> Churchlist t u
copyChurchlist xs = xs cons nil

Now, to implement map, you just need to replace cons with a suitable function, like this:

map :: (a -> b) -> [a] -> [b]
map f xs = foldr (\x xs' -> f x:xs') [] xs

Mapping is like copying a list, except that instead of just copying the elements verbatim you apply f to each of them.

Study all of this carefully, and you should be able to write mapChurchlist :: (t -> t') -> Churchlist t u -> Churchlist t' u on your own.

Extra exercise (easy): write these list functions in terms of foldr, and write counterparts for Churchlist:

filter :: (a -> Bool) -> [a] -> [a]
append :: [a] -> [a] -> [a]

-- Return first element of list that satisfies predicate, or Nothing
find :: (a -> Bool) -> [a] -> Maybe a

If you're feeling like tackling something harder, try writing tail for Churchlist. (Start by writing tail for [a] using foldr.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top