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 re-used.
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 tail-recursive 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 tail-recursive 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 (loop-invariant) parameter f
as an argument.
Example: write tail-recursive version of search_all : ’k -> (’k*’v) list -> ’v list
:
cs2041.org