evaluating
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.
The rules can lead to different behavior, e.g. …
Does call-by-name always use fewer evaluations?
Call-by-name will evaluate (fib 40)
twice.
We can avoid this using expression sharing, where expressions are graphs rather than trees.
Call-by-name with sharing is also called call-by-need or lazy evaluation.
lazyCaml
Imagine programming in lazyCaml
:
type 'a tree = Empty | Leaf of 'a | Node of ('a tree) * ('a tree)
let rec dfs t = match t with
| Empty -> []
| Leaf x -> [x]
| Node (lt,rt) -> (dfs lt) @ (dfs rt)
Suppose we want to decide if two trees result in the same depth-first search:
let rec listeq l1 l2 = match (l1,l2) with
| ([],[]) -> true
| ((h1::t1), (h2::t2)) -> h1=h2 && (listeq t1 t2)
| _ -> false
let eq_dfs t1 t2 = listeq (dfs t1) (dfs t2)
What happens when we evaluate:
eq_dfs Node(Node(Leaf 3, Leaf 7), Leaf 11) Node(Leaf 7, Node(Leaf 3, Leaf 11))
list_eq (dfs Node(Node(Leaf 3, Leaf 7), Leaf 11)) (dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
list_eq ((dfs Node(Leaf 3, Leaf 7)) @ (dfs Leaf 11)) (dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
list_eq ((dfs Leaf 3) @ (dfs Leaf 7) @ (dfs Leaf 11)) (dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
list_eq 3::((dfs Leaf 7) @ (dfs Leaf 11)) (dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
list_eq 3::((dfs Leaf 7) @ (dfs Leaf 11)) ((dfs Leaf 7) @ (dfs Node(Leaf 3, Leaf 11)))
list_eq 3::((dfs Leaf 7) @ (dfs Leaf 11)) 7::(dfs Node(Leaf 3, Leaf 11))
(3=7) && (list_eq ((dfs Leaf 7) @ (dfs Leaf 11)) (dfs Node(Leaf 3, Leaf 11)))
false
eq_dfs Node(Node(Leaf 7, Node(Leaf 3, Leaf 11)),Leaf 2)
Node(Leaf 7, Node(Leaf 3, Leaf 11))
≡ list_eq (dfs Node(Node(Leaf 7, Node(Leaf 3, Leaf 11)),Leaf 2))
(dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
≡ list_eq ((dfs Node(Leaf 7, Node(Leaf 3, Leaf 11))) @ (dfs Leaf 2))
(dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
≡ list_eq ((dfs Leaf 7) @ (dfs Node(Leaf 3, Leaf 11)) @ (dfs Leaf 2))
(dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
≡ list_eq 7::((dfs Node(Leaf 3, Leaf 11)) @ (dfs Leaf 2))
(dfs Node(Leaf 7, Node(Leaf 3, Leaf 11)))
≡ list_eq 7::((dfs Node(Leaf 3, Leaf 11)) @ (dfs Leaf 2))
((dfs Leaf 7) @ (dfs Node(Leaf 3, Leaf 11))
≡ list_eq 7::((dfs Node(Leaf 3, Leaf 11)) @ (dfs Leaf 2))
7::(dfs Node(Leaf 3, Leaf 11))
≡ list_eq ((dfs Node(Leaf 3, Leaf 11)) @ (dfs Leaf 2)) (dfs Node(Leaf 3, Leaf 11))
≡ list_eq ((dfs Leaf 3) @ (dfs Leaf 11)) @ (dfs Leaf 2)) (dfs Node(Leaf 3, Leaf 11))
≡ list_eq 3::((dfs Leaf 11) @ (dfs Leaf 2)) (dfs Node(Leaf 3, Leaf 11))
≡ list_eq 3::((dfs Leaf 11) @ (dfs Leaf 2)) ((dfs Leaf 3) @ (dfs Leaf 11))
≡ list_eq 3::((dfs Leaf 11) @ (dfs Leaf 2)) 3::(dfs Leaf 11)
≡ list_eq ((dfs Leaf 11) @ (dfs Leaf 2)) (dfs Leaf 11)
≡ list_eq 11::(dfs Leaf 2) (dfs Leaf 11)
≡ list_eq 11::(dfs Leaf 2) 11::[]
≡ list_eq (dfs Leaf 2) []
≡ list_eq 2::[] []
(we could write an eager OCaml function to compare trees this way…)
Also with lazy evaluation:
let rec nats n = n :: (nats (n+1))
let rec take n lst = match (n,lst) with
| (0,_) | (_,[]) -> []
| (_,(h::t)) -> h::(take (n-1) t)
Then (take 2 (nats 0))
is ok, even though (nats 0)
doesn’t terminate:
(take 2 (nats 0))
(take 2 (0 :: (nats 1)))
0::(take 1 (nats 1))
0::(take 1 (1::(nats 2)))
0::1::(take 0 (nats 2))
0::1::[]
Other infinite objects:
let rec squares n = (n*n) :: (squares (n+1))
let rec sum = function
| [] -> 0
| x::xs -> x + sum xs
evaluating (sum (take 3 (squares 3)))
:
sum (take 3 (9::(squares 4)))
sum 9::(take 2 (squares 4))
9+(sum (take 2 (squares 4)))
9+(sum (take 2 (16::(squares 5))))
9+(sum 16::(take 1 (squares 5)))
9+16+(sum (take 1 (squares 5)))
25+(sum (take 1 (squares 5)))
25+(sum (take 1 25::(squares 6)))
25+(sum 25::(take 0 (squares 6)))...
Even with lazy evaluation, some expressions will fail to terminate, e.g.
List.exists ((>) 0) (nats 0)
List.exists ((>) 0) 0::(nats 1)
(0 > 0) || (List.exists ((>) 0) (nats 1))
false || (List.exists ((>) 0) (nats 1))
List.exists ((>) 0) (nats 1)
List.exists ((>) 0) 1::(nats 2)
(0 > 1) || (List.exists ((>) 0) (nats 2))
...
And
let rec whee x = whee (x+1)
List.exists (whee) [1]
≡ (whee 1) || (List.exists whee [])
≡ (whee 2) || (List.exists whee [])
≡ (whee 3) || (List.exists whee [])
...
cs2041.org