Lists

There are many useful Lambda Calculus functions that are commonly defined on lists.

To append two lists, L1 and L2, if L1 is empty return L2, otherwise the result is the first element of L1 (i.e., hd L1) followed by the result of appending the tail of L1 and all of L2 (i.e., append (tl L1) L2).

append = lambda L1. lambda L2.
  if null L1 then L2
  else (hd L1) :: (append (tl L1) L2)

Zip takes two lists and returns a list of pairs.

zip = lambda L1. lambda L2.
  if null L1 or null L2 then nil
  else (pair hd L1 hd L2)::(zip tl L1 tl L2)

This version of merge allows duplicate entries that are common to L1 and L2; it is assumed that L1 and L2 are sorted in which case the result is sorted.

merge = lambda L1. lambda L2.
  if null L1 then L2
  else if null L2 then L1
  else { neither L1 nor L2 is null }
  if hd L1 < hd L2 then
    hd L1 :: merge tl L1 L2
  else { NB. duplicates allowed }
    hd L2 :: merge L1 tl L2

The slow version of reverse takes O(|L|2)-time. This is because append L1 L2 takes O(|L1|)-time, and reverseS calls append |L|-times.

reverseS = lambda L.  { O(|L|**2)-time }
  if null L then nil
  else append (reverseS tl L) (hd L :: nil)

The fast version of reverse takes O(|L|)-time by using an accumulating output parameter.
 
reverse = lambda L.
  let rec rev = lambda inp. lambda op.
  { op is an `accumulating parameter' }
    if null inp then op
    else rev (tl inp) ((hd inp)::op)
         { inp shrinks   op grows  }
  in rev L nil

Filter builds a sublist of those elements of a list, L, that satisfy a predicate (test, truth function), p.

filter = lambda p. lambda L.
{ those elements of L that satisfy p }
  if null L then nil
  else let hdL = hd L in
  if p hdL then hdL::(filter p tl L)
  else filter p tl L

Map applies a function, f, to every element of a list L. Map is also known as apply-to-all.

map = lambda f. lambda L.
  { [f L1, f L2, ..., f Ln] }
  if null L then nil
  else (f hd L)::(map f tl L) 

Foldl, fold-left, applies a binary operator, f, in a left-associative way, to all the elements of a list L, e.g., foldl times 1 (2::3::4::nil) = ((1×2)×3)×4.

foldl = lambda f. lambda z. lambda L.
  { f( ... (f (f z L1) L2) ...) Ln }
  let rec ff = lambda inp. lambda ans.
    if null inp then ans
    else ff tl inp (f ans hd inp)
  in ff L z

Foldr, fold-right, applies a binary operator, f, in a right-associative way, to all the elements of a list L.

foldr = lambda f. lambda z. lambda L.
  { f L1 (f L2 ( ... (f Ln z) ) ) }
  let rec ff = lambda L.
    if null L then z
    else f hd L (ff tl L)
  in ff L

(The list data structure is either built-in, or can be easily defined, in most programming languages that are based on the λ-calculus. However, lists can be defined, from scratch, in the pure λ-calculus.)

The following form exercises the above functions; change the final expression and experiment.