CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

intro Ocaml: Tail Recursion and Nested Functions

OCaml programs

Expressions

A program is a sequence of expressions that are evaluated in order.

Every expression has a unique value and type.

We’ve seen primitive types and operators, let (rec) expressions, function values and types, and product types (tuples).

Ocaml has a language for specifying types: ->, *, 'a.

Structured data (tuples, lists) can be accessed through pattern matching.

List example: reverse : 'a list -> 'a list

let rec reverse = function
| [] -> []
| h::t -> (reverse t) @ [h]

What if we evaluate reverse [1; 2; …; 𝓃]?

reverse [2;3; …; 𝓃] @[1]

reverse [3; …; 𝓃] @[2]

reverse [], @[𝓃]

[𝓃; …; 3; 2] @ [1]

[𝓃; …; 3] @ [2]

[] @ [𝓃]

How would you do it in python?

def reverse(lst):
  res = None
  while lst != None:
    res = cons(lst.head, res)
    lst = lst.tail
  return res
lst head:“1”
tail:→
head:“2”
tail:→
head:“3”
tail:→
None
None head:“1”
tail:←
head:“2”
tail:←
head:“3”
tail:←
res

We can express a similar algorithm in Ocaml:

let rec tail_rev lst res =
  match lst with
  | [] -> res
  | h::t -> tail_rev t (h::res)
  tail_rev 1::2::3::[] []
≡ tail_rev 2::3::[] 1::[]
≡ tail_rev 3::[] 2::1::[]
≡ tail_rev [] 3::2::1::[]
3::2::1::[] ≡ [3;2;1]

Tail recursion

let rec tail_rev lst res = match lst with
| [] -> res
| h::t -> tail_rev t (h::res)

tail_rev never returns control to recursive caller.

Functions defined this way are called tail recursive.

Since the stack frame doesn’t need to be restored, it can be re-used.

The compiled code is equivalent to the python algorithm.

def fact(n):
  res = 1
  while n != 0:
    res = n*res
    n = n-1
  return res
let rec tail_fact n res =
  if n = 0 then res
  else tail_fact (n-1) n*res
  tail_fact 4 1
≡ tail_fact 3 4
≡ tail_fact 2 12
≡ tail_fact 1 24

res builds up or “accumulates” the result, so is often called an accumulator.

Any while loop can be transformed to tail recursion in this way.

Example: Write a tail-recursive definition for length : 'a list -> int:

let rec tail_len lst len = match lst with
| [] -> len
| _::t -> tail_len t (len+1)

What happens if we call tail_len [1;2;3] 1337?

Nested Functions

Calling tail_fact or tail_rev with the wrong initial accumulator will yield an incorrect result.

Fortunately, since functions are values in OCaml, we can locally define the helper function for a tail-recursive implementation, e.g.:

let reverse lst =
   let rec tail_rev l acc = match l with
   | [] -> acc
   | h::t -> tail_rev t (h::acc)
in tail_rev lst []

Note: the OCaml List module includes this function as List.rev

Example sumf: (int->int) -> int -> int from LabEx1:

let sumf f n =
  let rec sumhelp i res = if i = 0 then (res + (f 0))
      else sumhelp (i-1) (res + (f i))
in sumhelp n 0

Because sumhelp is in the scope of the parameters to sumf, it does not need to have the (loop-invariant) parameter f as an argument.

Example: write tail-recursive version of search_all : ’k -> (’k*’v) list -> ’v list:

let search_all key lst =
  let rec tail_sa l acc = match l with
    | [] -> acc
    | (k,v)::t -> if k=key then tail_sa t (v::acc)
                  else tail_sa t acc
  in List.rev (tail_sa lst [])

cs2041.org

// reveal.js plugins