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 24res 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