Evaluation
Eager
Let's look at eager evaluation with respect to a function application.
- Eval the expr representing the function
- Eval each arguments expression from left to right
- Bind the parameters to the respective arguments and evaluate the body
The fallout of eager is that every time a function call occurs all arguments are evaluated even if they're not used in the body of a function. If an argument is repeatedly used, it's only evaluated once (we can optimize)
Lazy
- Eval the expression representing the function
- Bind the parameters to the respective un-evaluated argument expressions
- Evaluate the function body with the bindings
The fallout of lazy evaluation is that on the arguments that are used in the function are actually evaluated which may save time, but an expression that is used rpeatedly in the function is repeatedly evaluated, which could use more time.
The Difference
One big example would be errors, in eager, they will be found before the function body is executed whereas in lazy, the error arises inside the body of the function.
((λ (x) (* x x)) (/ 1 0)) ; eager: fails in arguments before function ever called
((λ (x) (* x x)) (/ 1 0)) ; lazy: tries to do (* (/ 1 0 ) (/ 1 0)) and then finds div by zero.
Delaying Evaluation in Eager
Recall that the body of a function is not evaluated until it is called. We can abuse this to create lazy evaluation inside of an eager evaluated language.
The idea of a thunk is to be able to have expressions which aren't evaluated until we want them to be, which can allow us to safely handle expressions which if evaluated would evaluate forever such as streams.
Free Identifiers and Closures
(define x 3)
(define a-thunk (λ () (+ x 1)))
In the above, is a free identifier in a-thunk
Note that the below is a closure as it has a free identifier, namely a or b
(λ (x y z) (a + b))
We say that this closure closes over a and b.