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
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?
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:
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_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 reused.
The compiled code is equivalent to the python algorithm.
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 tailrecursive definition for length : 'a list > int
:
What happens if we call tail_len [1;2;3] 1337
?
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 tailrecursive implementation, e.g.:
Note: the OCaml List
module includes this function as List.rev
Example sumf: (int>int) > int > int
from LabEx1:
Because sumhelp
is in the scope of the parameters to sumf
, it does not need to have the (loopinvariant) parameter f
as an argument.
Example: write tailrecursive version of search_all : ’k > (’k*’v) list > ’v list
:
cs2041.org