Eager and Lazy Evaluation

Evaluation Strategies

- fun constant (x) = 1;

> val constant = fn : 'a -> int

- fun cube (x : int) = x * x * x;

> val cube = fn : int -> int

Call by value: (value of argument of a function is evaluated)

Case1: Call to constant function

- val p = constant (cube (cube (cube (2))));

> val p = 1

Evaluation of the function is done as follows:

p = constant (cube (cube (cube (2))))

= constant (cube (cube (2*2*2))) = constant (cube (cube (8)))

= constant (cube (8*8*8))

= constant (cube (512)) = constant (512*512*512)

= constant (134217728) = 1

Case2: Call to cube function.

- val q = cube (cube (cube (2)));

> val q = 134217728 : int

Evaluation:

q = cube (cube (cube (2)))

= cube (cube (2*2*2)) = cube (cube (8))

= cube (8*8*8) = cube (512)

= 512*512*512 = 134217728

Call by name: (outermost reduction order).

Case1: Call to constant function

- val p = constant (cube (cube (cube (2))));

> val p = 1 at one step

Case2: Call to cube function

- val q = cube (cube (cube (2)));

> val q = 134217728 : int

Evaluation:

q = cube (cube (cube (2))) outermost cube is evaluated

= cube (cube (2)) * cube (cube (2)) * cube (cube (2))

= cube (2) * cube (2) * cube (2) * cube (cube (2)) * cube (cube (2))

= (2*2*2) * cube (2) * cube (2) * cube (cube (2)) * cube (cube (2))

= 8 * (2*2*2) * cube (2) * cube (cube (2)) * cube (cube (2))

= 64 * (2*2*2) * cube (cube (2)) * cube (cube (2))

Call by need: (similar to call by name except that the repetitions are avoided )

Case1:

- val p = constant (cube (cube (cube (2))));

> val p = 1 at one step

Case2:

- val q = cube (cube (cube (2)));

> val q = 134217728 : int

Evaluation:

q = cube (cube (cube (2)))

x = cube (cube (2))

Let y = cube (2) = 8

Then, x = cube (y) = 512

q = cube (x)

q = x * x * x

q = 512 * 512 * 512 = 134217728

Eager Evaluation Strategy

Lazy Evaluation Strategy

Conditional expression Evaluated value

If E = true then E1 else v E1

If E = false then v else E2 E2

If E = v then E1 else E2 v

- fun conditional (true, x, y) = x

| conditional (false, x, y) = y

> val conditional = fn : bool * 'a * 'a -> 'a

 

Lazy Evaluation in SML

Conditional expression

if E then E1 else E2

- if true then 4 else 23 div 0 ;

> val it = 4 : int

- if false then (4 div 0) else 5;

> val it = 5 : int

Boolean Operators

- infix andalso

- fun true andalso b = b

| false andalso b = false

- false andalso 4 > (6 div 0) ;

> val it = false : bool

- true orelse 4 > (6 div 0) ;

> val it = true : bool

Delaying Evaluation of an expression

- fun delay ( ) = Exp

> val delay = fn : unit -> 'a

Here 'a is the type of an expression Exp. An application of delay to an empty tuple will require an evaluation of Exp.

Simulation of Normal Order Evaluation

- fun compute (x, y) = if null (x) then [] else hd(x) :: y ;

> val compute = fn : 'a list * 'a list -> 'a list

- val p = compute([3,4,5],[6,7,8,9]);

> val p = [3,6,7,8,9] : int list

- val q = compute ([], [1,2,3,4,5]);

> val q = [ ] : int list

- fun delayf (fun1, fun2) = if null ( fun1( ) ) then [] else hd(fun1( )):: fun2( );

> val delayf = fn : (unit -> 'a list) * (unit -> 'a list) -> 'a list

Calls to both the functions are: compute(a, b) and delayf (( fn( ) Þ a), (fn( ) Þ b)).

- val x =[3,4,5];

> val x = [3,4,5] : int list

- val y = [6,7,8,9];

> val y = [6,7,8,9] : int list

- fun fun1( ) = x;

> val fun1 = fn : unit -> 'a list

- fun fun2( ) = y;

> val fun2 = fn : unit -> 'a list

- val p = delayf ( (fn( ) =>x), (fn( ) =>y) );

> val p = [3,6,7,8,9] : int list

- val q = delayf ((fn( ) =>y), (fn( ) =>p) );

> val q = [6,3,6,7,8,9] : int list

- val r = delayf ((fn ( ) => [ ] ), (fn ( ) => q));

> val r = [ ] : int list

OR

- val p = compute ( x, y );

> val p = [3,6,7,8,9] : int list

- val q = compute (y, p);

> val q = [6,3,6,7,8,9] : int list

- val r = compute([ ], q);

> val r = [ ] : int list

Non lazy version:

- fun comp (x, y) = if null(x) then [] else y @ y @y;

> val comp = fn : 'a list * 'b list -> 'b list

Simulated version:

- fun delay1 (fun1,fun2) = if null(fun1( )) then [] else fun2( ) @ fun2( ) @ fun2( );

> val delay1 = fn : (unit -> 'a list) * (unit -> 'b list) -> 'b list

Lazy version:

- fun delay_lazy (fun1,fun2) = if null(fun1( )) then [] else let val temp = fun2 ( )

in temp @ temp @ temp end;

> val delay_lazy = fn : (unit -> 'a list) * (unit -> 'b list) -> 'b list

- fun cond (x, y, z) = if x then y( ) else z( );

> val cond = fn : bool * (unit -> 'a) * (unit -> 'a) -> 'a

- val n = cond (true, (fn( ) => 4), (fn( ) => 34 div 0)) ;

> val n = 4 : int

- val n = cond (false, (fn() => 4 div 0 ), (fn() => 34 ) );

> val n = 34 : int

Advantages of Lazy evaluation

For illustration, the functions are written in SML.

Avoids unnecessary computation

- infix equal;

> infix equal

- fun ([] equal []) = true

| ((a::x) equal (b::y)) = (a = b) andalso (x equal y)

| ((a::x) equal []) = false

| ([] equal (b::y)) = false;

> val equal = fn : ''a list * ''a list -> bool

- datatype 'a BT = Null | Node of ‘a BT * ‘a * 'a BT;

> datatype 'a BT = Null | Node of ‘a BT * ‘a * 'a BT

- fun preorder Null = []

| preorder (Node (left, data, right))= [data] @ preorder left @ preorder right;

> val preorder = fn : 'a BT -> 'a list

- fun tequality(t1, t2) = preorder (t1) equal preorder (t2); {list equality}

> val tequality = fn : ''a btree * ''a btree -> bool

- val t = Node(Node (Node (Null, 3, Null), 2, Null), 1, Node(Node(Null,5, Null),

4, Node(Null, 6, Null)));

> val t = Node(Node (Node #, 2, Null), 1, Node (Node #,4,Node #)) : int BT

- val t1 = Node(Node (Null, 2, Null),1, Node(Node(Null,4, Null),3, Node(Node(Null,6,Null), 5, Null)));

> val t1= Node(Node (Node #, 2, Null), 1, Node (Node #,3,Node #)) : int BT

- val x = tequality (t, t1);

> val x = true : bool

- tequality (Node(Null, 6, Null), Node (Node(Null, 4, Null), 2, Null))

> val it = false : bool

Evaluation: tequality (Node(Null, 6, Null), Node (Node(Null, 4, Null), 2, Null))

= preorder (Node(Null, 6, Null)) equal preorder (Node (Node(Null, 4, Null), 2, Null))

= [6] @ preorder(Null) @ preorder(Null) equal [2] @ preorder(Node (Node(Null, 4, Null)) @ preorder(Null)

= (6:: [] @ preorder(Null) @ preorder(Null) equal (2 :: [] @ preorder(Node (Node(Null, 4, Null)) @ preorder(Null)

= (6 = 2) andalso ([] @ preorder(Null) @ preorder(Null) ) equal ([] @ preorder(Node (Node(Null, 4, Null)) @ preorder(Null)

= false andalso ...... equal .......

= false

Avoids non termination

- fun conditional (x, y, z) = if x then y else z;

> conditional = fn : bool * 'a * 'a -> 'a

- fun fact n = conditional (n=0, 1, n * fact(n-1));

> val fact = fn : int -> int

Eager evaluation:

- val x = fact (0);

> Non terminating

- val y = fact (2);

> Non terminating

Evaluation:

fact (0) = conditional (true, 1, 0 * fact(-1) )

It is non terminating.

y = fact(2)

= conditional(false, 1, 2 * fact(1))

= conditional (false, 1, 2 * conditional (false, 1, 1 * fact(0)))

Because of fact (0) in the argument of conditional it goes into non termination as explained above,

Lazy evaluation:

- val x = fact (0);

> val x = 1 : int

- val x = fact (4);

> val y = 24 : int

- fun fact n = cond ( n = 0, (fn() => 1), (fn() => n* fact (n-1)));

> val fact = fn : int -> int

- fact 0;

> val it = 1 : int

- fact 4;

> val it = 24 : int

- fun gen(n) = n::gen(n+1);

> val gen = fn : int -> int list

- fun get (1, x::xs) = [x]

| get (n, []) = [0]

| get (n, x::xs) = x:: get(n-1, xs);

> val get = fn : int * int list -> int list

Eager evaluation:

- val x = get (3, gen(1) );

> non termination

Evaluation:

x = get (3, gen(1) ) = get(3, 1 :: gen(2))

= get(3, 1 :: 2 :: gen(3)) = get(3, 1 :: 2 :: 3 :: gen(4))

Lazy evaluation:

- val x = get (3, gen(1) );

> val x = [1,2,3]

Evaluation:

x = get(3, gen(1)) = get(3, 1 :: gen(2))

= 1 :: get(2, gen(2)) = 1 :: get(2, 2::gen(3))

= 1 :: 2 :: get(1, gen(3)) = 1 :: 2 :: get(1, 3::gen(4))

= 1 :: 2 :: [3] = [1,2,3]

Number = 2n * 3m , " n, m ³ 0

The list of numbers can be defined informally as:

  1. 1 is valid number,
  2. if x is a valid number, then 2x and 3x are also valid numbers.

- fun seq ( ) = get_next [1];

> val seq = fn : unit -> int list

- fun get_next (x :: xs) = x :: get_next (merge ( [2*x, 3*x], xs ));

> val get_next = fn : int list -> int list

Eager evaluation:

- val t = get (5, seq( ));

> non termination

Lazy evaluation:

- val t = get (5, seq( ));

> val t = [1,2,3,4,6] : int list

Evaluation:

t = get (5, seq ( ))

= get(5, get_next [1])

= get (5, 1 :: get_next (merge([2,3], [])))

= 1 :: get (4, get_next ([2,3]))

= 1 :: get (4, 2 :: get_next(merge([4,6], [3]) ))

= 1 :: 2 :: get(3, get_next([3,4,6]))

= 1 :: 2 :: get(3, 3 :: get_next(merge([6,9], [4,6])))

= 1 :: 2 :: 3 :: get (2, get_next ([4,6,9] ) )

= 1 :: 2 :: 3 :: get (2, 4 :: get_next (merge([8,12], [6,9])))

= 1 :: 2 :: 3 :: 4 :: get (1, get_next([6, 8, 9, 12]))

= 1 :: 2 :: 3 :: 4 :: get (1, 6 :: get_next(merge ([12, 18],[8, 9, 12])))

= 1 :: 2 :: 3 :: 4 :: 6

= [1,2,3,4,6]

  1. Initially, assume that 2 is a prime number.
  2. Find prime numbers by removing the non prime numbers from a list of integers generated from 2 onwards.
  3. Non prime number is one which is a multiple of some previously generated prime number.

Alternatively, we can state the method as follows:

- fun not_multiple x y = (y mod x <> 0);

> val not_multiple = fn : int -> int -> bool

- fun filter f [] = []

| filter f (x::xs) = if f (x) then x :: filter f xs else filter f xs;

> val filter = fn : ('a -> bool) -> 'a list -> 'a list

- fun remove x [] = []

| remove x xs = filter (not_multiple x) xs;

> val remove = fn : int -> int list -> int list

- fun main (x ::xs) = x :: main(remove x xs);

> val main = fn : int list -> int list

- fun primes ( ) = main (gen 2);

> val primes = fn : unit -> int list

Eager evalaution:

- get(4, primes( ));

> non terminating

Lazy evaluation:

- get(4, primes( ));

> val it = [2,3,5,7] : int list

Handling of infinite data structures

- val rec inf_list = 1:: inf_list;

> val rec inf_list = 1:: inf_list : int LIST

- val x = get(10, inf_list);

> val x = [1,1,1,1,1,1,1,1,1,1] : int list

- val rec pair_list = (1,2) :: pair_list;

> val rec pair_list = (1, 2) :: pair_list : (int * int) LIST

- val y = inf_list equal pair_list;

> val y = false : bool

- val z = get(3, pair_list);

> val z = [(1,2), (1,2), (1,2)] : int list

Cyclic structures

- val rec inf_tree = Node (inf_tree, 6, inf_tree);

> val rec inf_tree = Node (inf_tree, 6, inf_tree) : int BT

- val x = get (5, preorder (inf_tree));

> val x = [6,6,6,6,6] : int list

- val rec t = Node (Node (Null, 4, Null), 5, t);

> val rec t = Node (Node (Null, 4, Null), 5, t) : int BT

Number = 2n * 3m , " n, m ³ 0

This problem has also been discussed earlier and was solved by using recursive function. Alternatively the problem is stated as:

  1. 1 is valid number,
  2. if x is a valid number, then 2x and 3x are also valid numbers.

- val rec seq_num = 1:: merge ((map(times 2) seq_num), (map(times 3) seq_num))

> val rec seq_num = 1:: merge ((map(times 2) seq_num), (map(times 3) seq_num)):int list

The function map multiplies 2 or 3 whichever the case might be to the head of recursive list seq_num whenever there is a need. It is defined as follows:

- fun map f [] = []

| map f (x :: xs) = f x :: map f xs ;

> val map = fn : ('a -> 'b) -> 'a list -> 'b list

seq_num = 1:: merge ((map(times 2) seq_num), (map(times 3) seq_num))

= 1 :: merge (2 :: (map(times 2) seq_num), 3 :: (map(times 3) seq_num))

= 1 :: 2 :: merge ((map(times 2) seq_num), 3 :: (map(times 3) seq_num))

= 1::2::merge (4 :: (map(times 2) seq_num), 3 :: (map(times 3) seq_num))

= 1::2::3 :: merge (4 :: (map(times 2) seq_num), (map(times 3) seq_num))

Lazy evaluation for generating solution using numerical techniques

x2 - a = 0 Þ x2 = a Þ x = Ö a

xi = (x(i-1) + a / x(i-1) ) / 2.0

Termination condition: (xi - x(i-1) ) < .000001

- fun gen (a, n) = n :: gen(a, (n + a / n) / 2.0);

> val gen = fn : real * real -> real list

- fun findroot (a) = gen (a, 1.0);

> val findroot = fn : real -> real list

- fun terminate(x::y::xs) = if (x-y ) < 0.00001 then x else terminate (y :: xs);

> val terminate = fn : real list -> real

- fun sqrt (a) = terminate (findroot (a));

> val sqrt = fn : real -> real

Separation of data and control

- fun cube [] = []

| cube (n:: ns) = n*n*n :: cube ns;

> val cube = fn : int list -> int list

- fun double [] = []

| double (n::ns) = n + n :: double ns;

> val double = fn : int list -> int list

- fun positive x = if x > 0 then true else false;

> val positive = fn : int -> bool

- fun main ( ) = double (cube(filter positive (get_list)));

> val main = fn : unit -> int list

 Interactive program

rsponses -> function -> requests

 Simulation and Implementation of infinite data structures in SML

Lists with lazy evaluation mechanism

- datatype 'a lazy_list = ML_list of (unit ->'a * 'a lazy_list) | E_list;

> datatype 'a lazy_list = E_list | ML_list of unit -> 'a * 'a lazy_list

- fun cons x xs = let fun f( ) = (x, xs) in ML_list f end;

> val cons = fn : 'a -> 'a lazy_list -> 'a lazy_list

- fun head E_list = ~1

| head (ML_list f) = let val (x, xs) = f( ) in x end;

> val head = fn : int lazy_list -> int

- fun tail E_list = E_list

| tail (ML_list f ) = let val (x, xs) = f( ) in xs end;

> val tail = fn : 'a lazy_list -> 'a lazy_list

- fun null E_list = true

| null (ML_list f) = false;

> val null = fn : 'a lazy_list -> bool

- infix eq

> infix eq

- fun (E_list eq E_list ) = true

| (ML_list f) eq (ML_list g) = let val (x, xs) = f( ); val (y, ys) = g( )

in (x = y) andalso (xs eq ys ) end

| ((ML_list f) eq E_list) = false

| (E_list eq (ML_list f)) = false;

> val eq = fn : ''a lazy_list * ''a lazy_list -> bool

- cons 4 E_list;

> val it = ML_list fn : int lazy_list

- cons 4 it;

> val it = ML_list fn : int lazy_list

- cons 7 it;

> val it = ML_list fn : int lazy_list

- val p = cons 8 it;

> val p = ML_list fn : int lazy_list

- head p;

> val it = 8 : int

- tail p;

> val it = ML_list fn : int lazy_list

- p eq it;

> val it = false : bool

- val q = p;

> val q = ML_list fn : int list1

- p eq q;

> val it = true : bool

Construction of infinite lists: (list of ones, list of natural numbers etc.)

- val inf_ones = let fun f( ) = (1, ML_list f) in ML_list f end;

> val inf_ones = ML_list fn : int lazy_list

- fun gen n = let fun f ( ) = (n, gen (n+1)) in ML_list f end;

> val gen = fn : int -> int lazy_list

- val nat = gen 1;

> val nat = ML_list fn : int lazy_list

- val from10 = gen 10;

> val from10 = ML_list fn : int list1

- fun front n s = get_first(lazy_get n s);

> val front = fn : int -> 'a lazy_list -> 'a list

where get_first and lazy_get are defined below:

- fun lazy_get 0 s = ([], s)

| lazy_get n E_list = ([],E_list)

| lazy_get n (ML_list f) = let val (x, xs) = f();

val (y,ys) = lazy_get (n-1) xs in (x::y, xs) end;

> val lazy_get = fn : int -> 'a lazy_list -> 'a list * 'a lazy_list

- fun get_first (x, y) = x ;

> val get_first = fn : 'a * 'b -> 'a

- front 5 inf_ones;

> val it = [1,1,1,1,1] : int list

- front 10 inf_ones;

> val it = [1,1,1,1,1,1,1,1,1,1] : int list

- front 6 nat;

> val it = [1,2,3,4,5,6] : int list

- front 10 from10;

> val it = [10,11,12,13,14,15,16,17,18,19] : int list

Tree comparison using lazy evaluation mechanism

- datatype 'a BT = Null | Node of 'a BT * 'a * 'a BT;

> datatype 'a BT = Node of 'a BT * 'a * 'a BT | Null

- fun stack_compare [] [] = true

| stack_compare [] (a::x) = if a = Null then stack_compare [] x else false

| stack_compare (a::x) [] = if a = Null then stack_compare x [] else false

| stack_compare (Null::x) y = stack_compare x y

| stack_compare x (Null::y)= stack_compare x y

| stack_compare (Node(l1,d1,r1)::x) (Node(l2,d2,r2)::y) = if (d1 = d2) then stack_compare (l1 ::r1::x) (l2::r2::y) else false;

> val stack_compare = fn : ''a BT list -> ''a BT list -> bool

- fun tree_eq t1 t2 = stack_compare [t1] [t2];

> val tree_eq = fn : ''a BT -> ''a BT -> bool

- val t = Node(Node(Node (Null,3,Null),2,Null),1, Node(Node(Null,5, Null), 4, Node(Null, 6, Null)));

> val t = Node (Node (Node #,2,Null),1,Node (Node #,4,Node #)) : int BT

- val t1= Node (Node (Null,1,Null),2,Node(Node(Null,4, Null, 3, Node

(Node(Null,6,Null), 5, Null)));

> val t1 = Node (Node (Null,2,Null),1,Node (Node #,3,Node #)) : int BT

- val x = Node(Node(Node (Null,2,Null),1,Node(Node(Null,5, Null), 4, Null));

> val x = Node (Node (Null,2,Null),1,Node (Node #,4,Null)) : int BT

- val y = Node (Node (Null,1,Null),2,Node(Null, 3, Null));

> val y = Node (Node (Null,1,Null),2,Node (Null,3,Null)) : int BT

- val p = tree_eq t t1;

> val p = true : bool

- val q = tree_eq x y;

> val q = false : bool

Construction of infinite trees and their comparisons

- datatype 'a BT = Null | Node of (unit -> 'a BT * 'a * 'a BT);

> datatype 'a BT = Node of unit -> 'a BT * 'a * 'a BT | Null

- fun delayed_insert(key, Null) = let fun f() = (Null, key, Null) in Node f end

| delayed_insert (key, Node f) = let val (left, k, right) = f ( ) in if key < k then let

fun g ( )= (delayed_insert (key,left), k,right) in Node g end else let fun h( ) = (left, k, delayed_insert (key, right)) in Node h end end;

> val insert = fn : int * int BT -> int BT

- fun preorder Null = []

| preorder (Node f)= let val (left, data, right) = f( ) in [data] @ preorder left @ preorder right end;

> val preorder = fn : 'a BT -> 'a list

- fun inorder Null = []

| inorder( Node f) = let val (left, data, right) = f( ) in inorder left @ [data] @ inorder right end;

> val inorder = fn : 'a BT -> 'a list

- fun postorder Null = []

| postorder(Node f) = let val (left, data, right) = f( ) in postorder left @ postorder right @ [data] end;

> val postorder = fn : 'a BT -> 'a list

Definitions of various infinite binary trees

- val tree6 = let fun f () = (Node f, 6, Node f) in Node f end;

> val tree6 = Node fn : int BT

- val tree23 = let fun g ( ) = (Node f, 2, Null) and f ( ) = (Node g, 3, Null) in Node g end;

> val tree23 = Node fn : int BT

- val t = let fun f ( ) = (Null, 1, Node f) in Node f end;

> val t = Node fn : int BT

- fun l_get 0 s = ([], s)

| l_get n Null = ([], Null)

| l_get n (Node f) = let val (l,d,r) = f( ); val (d1, xs) = if null l then l_get (n-1) r else l_get (n-1) l in (d::d1, l) end ;

> val l_get = fn : int -> 'a BT -> 'a list * 'a BT

- fun fst (x, y) = x ;

> val fst = fn : 'a * 'b -> 'a

- fun front n s = fst(l_get n s);

> val front = fn : int -> 'a BT -> 'a list

- front 6 t;

> val it = [1,1,1,1,1,1] : int list

- front 5 tree6;

> val it = [6,6,6,6,6] : int list

- front 8 tree23;

> val it = [2,3,2,3,2,3,2,3] : int list

- fun delayed_stack_eq [] [] = true

| delayed_stack_eq [] ((Node f)::x) = false

| delayed_stack_eq ((Node f)::x) [] = false

| delayed_stack_eq (Null::x) y = delayed_stack_eq x y

| delayed_stack_eq x (Null::y) = delayed_stack_eq x y

| delayed_stack_eq ((Node f)::x) ((Node g)::y) = let val (l1,d1,r1) = f(); val (l2,d2,r2) = g() in if (d1 = d2) then delayed_stack_eq (l1::r1::x) (l2::r2::y) else false end;

> val equal = fn : ''a BT list -> ''a BT list -> bool

- fun tree_equality t1 t2 = delayed_stack_eq [t1] [t2];

> val tree_equality = fn : ''a BT -> ''a BT -> bool

- tree_equality t tree6;

> val it = false : bool

Disadvantages of Lazy evaluation