CSCI 2041

ADVANCED PROGRAMMING PRINCIPLES

Evaluation Order

Evaluating Expressions

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)

if (n=0) then e1 else e2

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!

Evaluating Rewriting Expressions

Another way to think of evaluation: rewriting expressions (trees):

tree representation of (if (n=0) then 1 else n)*(3+6)

tree representation of (if false then 1 else n)*(3+6)

tree representation of (n)*(3+6)

tree representation of 4*(3+6)

tree representation of 4*9

tree representation of 36

…until we reach a normal form expression that can’t be reduced.

Starting from

tree representation of (f n) = if n=0 then 1 else n*(f (n-1))

With n=4 we eventually get to

tree representation of 4 * (f (n-1))

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:

let  (@@) f x = f x

tree representation of 4 * (f (n-1))

tree representation of 4 * (f @@ (n-1))

Now we need a rule for evaluating f @@ e.

There are two common rules for evaluating e1 @@ e2

Call by Value

  • Evaluate e2 to get value v
  • Evaluate e1 to get function body f
  • Evaluate f with param → v

Call by Name

  • Evaluate e1 to get function body f
  • evaluate 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

(take 2 (range 0 3)) ≡ (take @@ 2) @@ ((range @@ 0) @@ 3)

by value:

(take 2 (0::(range 1 3)))
(take 2 (0::(1::(range 2 3))))
(take 2 (0::1::2::(range 3 3)))
(take 2 (0::1::2::[]))
0::(take 1 1::2::[])
0::1::(take 0 2::[])
0::1::[]

by name:

(take 2 (0::(range 1 3)))
0::(take 1 (range 1 3))
0::(take 1 (1::(range 2 3)))
0::1::(take 0 (range 2 3))
0::1::[]

Evaluation stops when a normal form, or value is reached:

  • A constant belonging to a built-in type; or
  • A constructor applied to the correct number of arguments; or
  • A function applied to no arguments (i.e. 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.

The rules can lead to different behavior, e.g:

let rec f x y  = f y x in
let g z = "good!" in g (f 2 3)

cs2041.org

// reveal.js plugins