Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Overview
Elements of Programming Languages Lecture 15: Evaluation strategies and laziness Final few lectures: cross-cutting language design issues So far: Type safety James Cheney References, arrays, resources Today: University of Edinburgh Evaluation strategies (by-value, by-name, by-need) Impact on language design (particularly handling e↵ects) November 18, 2016
Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order Evaluation order
We’ve noted already that some aspects of small-step We can vary the evaluation order by changing the semantics seem arbitrary administrative rules. For example, left-to-right or right-to-left evaluation To evaluate right-to-left: Consider the rules for +, .Therearetwokinds: ⇥ e e0 e e0 computational rules that actually do something: 2 7! 2 1 7! 1 e e e e0 e v e0 v 1 2 7! 1 2 1 2 7! 1 2 v1 + v2 v1 +N v2 v1 v2 v1 N v2 7! ⇥ 7! ⇥ To leave the evaluation order unspecified: and administrative rules that say how to evaluate inside e e0 e e0 subexpressions: 1 7! 1 2 7! 2 e e e0 e e e e e0 1 2 7! 1 2 1 2 7! 1 2 e1 e10 e2 e20 7! 7! by lifting the constraint that the other side has to be a e1 e2 e10 e2 v1 e2 v1 e20 7! 7! value. Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Call-by-value Example
So far, function calls evaluate arguments to values before Consider ( x.x x)(1+2 3) ⇥ ⇥ binding them to variables Then we can derive:
e1 e10 e2 e20 2 3 6 7! 7! ⇥ 7! e1 e2 e10 e2 v1 e2 v1 e20 ( x. e) v e[v/x] 1+2 3 1+6 7! 7! 7! ( x.x x)(1+2 ⇥3) 7! ( x.x x)(1+6) This evaluation strategy is called call-by-value. ⇥ ⇥ 7! ⇥ Sometimes also called strict or eager Next: 1+6 7 “Call-by-value” historically refers to the fact that ( x.x x)(1+6)7! ( x.x x)7 expressions are evaluated before being passed as ⇥ 7! ⇥ parameters Finally: ( x.x x)7 7 7 49 It is the default in most languages ⇥ 7! ⇥ 7!
Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Interpreting call-by-value Call-by-name
Call-by-value may evaluate expressions unnecessarily We evaluate subexpressions fully before substituting them for (leading to nontermination in the worst case) variables: ( x.42) loop ( x.42) loop def eval (e: Expr): Value = e match { 7! 7! ··· ... An alternative: substitute expressions before evaluating case Let(x,e1,e2) => eval(subst(e2,eval(e1),x)) ... ( x.42) loop 42 7! case Lambda(x,ty,e) => Lambda(x,ty,e) To do this, remove second administrative rule, and case Apply(e1,e2) => eval(e1) match { generalize the computational rule case Lambda(x,_,e) => apply(subst(e,eval(e2),x)) e1 e10 } 7! e1 e2 e0 e2 ( x. e1) e2 e1[e2/x] } 7! 1 7! This evaluation strategy is called call-by-name (the “name” is the expression) Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Example, revisited Interpreting call-by-name
Consider ( x.x x)(1+2 3) ⇥ ⇥ We substitute expressions for variables before evaluating. Then in call-by-name we can derive: def eval (e: Expr): Value = e match { ( x.x x)(1+2 3) (1 + (2 3)) (1 + (2 3))) ... ⇥ ⇥ 7! ⇥ ⇥ ⇥ case Let(x,e1,e2 ) => eval(subst(e2,e1,x)) The rest is standard: ... case Lambda(x,ty,e) => Lambda(x,ty,e) (1 + (2 3)) (1 + (2 3)) (1 + 6) (1 + (2 3)) ⇥ ⇥ ⇥ 7! ⇥ ⇥ 7 (1 + (2 3)) case Apply(e1,e2) => eval(e1) match { 7! ⇥ ⇥ case Lambda(x,_,e) => eval(subst(e,e2,x)) 7 (1 + 6) 7! ⇥ } 7 7 49 } 7! ⇥ 7! Notice that we recompute the argument twice!
Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Call-by-name in Scala Simulating call-by-name
In Scala, can flag an argument as being passed by name by writing => in front of its type Such arguments are evaluated only when needed (but Using functions, we can simulate passing e : ⌧ by name in may be evaluated many times) acall-by-valuelanguage Simply pass it as a “delayed” expression scala> def byName(x : => Int) = x + x ().e : unit ⌧. byName: (x: => Int)Int ! scala> byName({ println("Hi there!"); 42}) When its value is needed, apply to (). Hi there! Scala’s “by name” argument passing is basically syntactic Hi there! sugar for this (using annotations on types to decide when res1: Int = 84 to silently apply to ())
This can be useful; sometimes we actually want to re-evaluate an expression (see next week’s tutorial) Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Comparison Best of both worlds?
Call-by-value evaluates every expression at most once ... whether or not its value is needed Performance tends to be more predictable Athirdstrategy:evaluateeachexpressionwhenitis Side-e↵ects happen predictably needed, but then save the result Call-by-name only evaluates an expression if its value is If an expression’s value is never needed, it never gets needed evaluated Can be faster (or even avoid infinite loop), if not needed If it is needed many times, it’s still only evaluated once. But may evaluate multiple times if needed more than once This is called call-by-need (or sometimes lazy)evaluation. Reasoning about performance requires understanding when expressions are needed Side-e↵ects may happen multiple times or not at all!
Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Evaluation order and call-by-value Call-by-name Call-by-need and lazy evaluation Laziness in Scala Laziness in Scala
Actually, laziness can also be emulated using references Scala provides a lazy keyword and variant types: Variables declared lazy are not evaluated until needed class Lazy[A](a: => A) { When they are evaluated, the value is memoized (that is, private var r: Either[A,() => A] = Right{() => a} we store it in case of later reuse). def force = r match { case Left(a) => a scala> lazy val x = {println("Hello"); 42} case Right(f) => { x: Int =