CSE 505 Lecture Notes:

Evaluation in Functional Languages

Sept. 30, 1994


Introduction

The evaluation of an expression in a functional language can be described as a rewriting of the expression into a canonical form. The function definitions are the rewrite rules. Rewrite a function application by replacing it with the body of the function, substituting actual arguments for formals.

Example:

given the definition double x = x+x evaluate double 4 double 4 => 4+4 => 8

Normal Order vs. Applicative Order Evaluation

In general there are many possible rewriting orders. Two important ones are "normal order" and "applicative order". In normal order, you rewrite the leftmost occurrence of a function application. In applicative order, you rewrite the innermost occurrence of a function application first. To specify this completely, pick the leftmost one, although it doesn't actually matter which you pick. (Why?)

Normal order always gives the same results as lazy evaluation, although it may end up evaluating expressions more times. It corresponds to call-by-name, which we'll discuss when we come to Algol 60.

Applicative order corresponds to call-by-value (again we'll talk about this more when we come to Algol-60).

Example 1:

consider double x = x+x average x y = (x+y)/2 to avoid confusion about infix notation, let's re-express this as double x = plus x x average x y = divide (plus x y) 2 evaluate double (average 2 4) using normal order evaluation: double (average 2 4) => plus (average 2 4) (average 2 4) => plus (divide (plus 2 4) 2) (average 2 4) => plus (divide 6 2) (average 2 4) => plus 3 (average 2 4) => plus 3 (divide (plus 2 4) 2) => plus 3 (divide 6 2) => plus 3 3 => 6 Notice that (average 2 4) was evaluated twice ... lazy evaluation would cache the results of the first evaluation. using applicative order evaluation: double (average 2 4) => double (divide (plus 2 4) 2) => double (divide 6 2) => double 3 => plus 3 3 => 6

Example 2:

Now consider: my_if True x y = x my_if False x y = y evaluate my_if (less 3 4) (plus 5 5) (divide 1 0) normal order evaluation: my_if (less 3 4) (plus 5 5) (divide 1 0) => my_if True (plus 5 5) (divide 1 0) => (plus 5 5) => 10 applicative order evaluation: my_if (less 3 4) (plus 5 5) (divide 1 0) => my_if True (plus 5 5) (divide 1 0) => my_if True 10 (divide 1 0) => DIVIDE BY ZERO ERROR Important properties:

If there is any evaluation order that will terminate and that will not generate an error, normal order evaluation will terminate and will not generate an error.

ANY evaluation order that terminates without error will give the same result as any other evaluation order that terminates without error.


The Dreaded Twice Twice Twice Example

Now let's tackle a simpler version of the twice twice twice example ... twice f x = f (f x) s n = plus n 1 twice twice s 0 => twice (twice s) 0 => (twice s) ((twice s) 0) ... or dropping the extra parens: twice s (twice s 0) => s (s (twice s 0)) => s (s (s (s 0))) => s (s (s 1)) => s (s 2) => s (3) => 4 Actually, this is cheating a bit -- I happen to know that both twice and s are *strict* in both of their arguments, so I switched to applicative order evaluation. The correct order of evaluation is really: twice twice s 0 => twice (twice s) 0 => (twice s) ((twice s) 0) ... or dropping the extra parens: twice s (twice s 0) => s (s (twice s 0)) => plus (s (twice s 0)) 1 => plus (plus (twice s 0) 1) 1 => plus (plus (twice s 0) 1) 1 => plus (plus (s (s 0)) 1) 1 => plus (plus (plus (s 0) 1) 1) 1 => plus (plus (plus (plus 0 1) 1) 1) 1 => plus (plus (plus 1 1) 1) 1 => plus (plus 2 1) 1 => plus 3 1 => 4 Definition:

A function is *strict* in an argument if that argument is always evaluated whenever an application of f is evaluated. If a function is strict in an argument, we can safely evaluate the argument first if we need the value of applying the function.