When OCaml (or interpreter.ml
) evaluates an expression, eg:
x + 7 * y
It (generates code that) evaluates x
, y
,7*val(y)
, and val(x)+val(7*y)
Evaluate (n=0)
, then if true, evaluate e1
, otherwise e2
.
let rec f n = if n=0 then 1 else n*(f (n-1))
must be evaluated in this order!
Another way to think of evaluation: rewriting expressions (trees):
…until we reach a normal form expression that can’t be reduced.
Starting from
With n=4
we eventually get to
How do we re-write (f (n-1))
? Considering also “chained applications” like ((f g) h)
or f (g h) n
…
Answer: the “application” operator @@
, defined by:
Now we need a rule for evaluating f @@ e
.
There are two common rules for evaluating e1 @@ e2
…
Call by Value
e2
to get value v
e1
to get function body f
f
with param → v
Call by Name
e1
to get function body f
f
with param → e2
The main difference is when evaluating applications to applications:
(fun y -> ... y ...) (g x)
≡ ( ... (g x) ...)
Example: using these definitions
let rec range i n = if i>=n then [] else i::(range (i+1) n)
let rec take n lst = match (n,lst) with
| (0,_) | (_,[]) -> []
| (n,(h::t)) -> h::(take (n-1) t)
and evaluating the expression
Evaluation stops when a normal form, or value is reached:
fun x -> e
)Call-by-value is also called applicative order or eager evaluation, and is the more common eval rule. (used in Python, Java, C/C++, OCaml,…)
Call-by-name is also called lazy or normal-order evaluation.
cs2041.org