• No results found

local contexts

In document Zen Style Programming (Page 35-42)

2. more interesting aspects

2.6 local contexts

Here is an expression with a local context:

(let ((topping-1 ’extra-cheese) (topping-2 ’pepperoni))

(list ’pizza ’with topping-1 ’and topping-2))

The variables topping-1 and topping-2 are created locally inside of let.

Let is a pseudo function of two arguments.

The first argument is called its environment and the second one its body (or term).

The environment of let is a list of two-member lists:

((name1 value1) ...

(namen valuen))

Let evaluates all values and then binds each value to the name associated with that value.

The term is evaluated inside of the context created by binding the values to names locally, and the normal form of the term is returned:

(let ((x ’heads) (y ’tails)) (cons x y))

=> ’(heads . tails)

Here is a let whose body is another let:

(let ((x ’outer)) (let ((x ’inner)

(y x)) (list x y)))

=> ’(inner outer)

There are two local contexts in this expression, an inner one and an outer one.

A context that is created by a let is called its inner context.

The context in which let occurs is called its outer context.

Let evaluates all values of its environment in its outer context:

(let ((xouter ’outer)) (let ((xinner ’inner)

(y xouter)) (list x y)))

=> ’(inner outer)

In case of a duplicate symbol, the term of the inner let can only refer to the inner instance of that symbol, and the environment of the inner let can only refer to its outer instance.

BTW, let is nothing but an alternative syntax for the application of an anonymous function:

(let ((hd ’heads) = ((lambda (hd tl) (tl ’tails)) (cons hd tl)) (cons hd tl)) ’heads

’tails)

This equivalence explains neatly why values in environments are evaluated in outer contexts.

You would not expect a lambda function to evaluate its arguments in its inner context, would you?

2.6.1 closures

In nested local contexts, inner values shadow outer values:

(let ((v ’outer)) (let ((v ’inner))

v))

=> ’inner

But what happens when a local variable occurs as a free variable in a lambda function?

(let ((v ’outer))

(let ((f (lambda () v))) (let ((v ’inner))

(f))))

=> ’outer

What you observe here is a phenomenon called lexical scoping.

The value of the free variable v inside of the function f depends on the lexical context in which the function is defined.

The definition of f takes place in a context where v is bound to ’outer, and the function f memorizes this binding.

The re-definition of v has no effect on it.

A function that memorizes the lexical values of its free variables is called a closure.

Here is a function that creates a closure:

(define (create-conser x) (lambda (y) (cons x y)))

The closure returned by the function conses x to its argument.

The value of x is specified when the closure is created:

(define cons-cherry (create-conser ’cherry)) (define cons-lemon (create-conser ’lemon))

The closures memorize the parameters passed to create-conser:

(cons-cherry ’pie) => ’(cherry . pie) (cons-lemon ’juice) => ’(lemon . juice)

Alright, once again in slow motion:

((lambda (x) (lambda (y) (cons x y))) ’lemon) ----> (lambda (y) (cons ’lemon y))

This is a step called beta reduction. It is not a reduction in the sense ofzenlisp, but a more abstract concept, as indicated by the generic arrow above.

Beta reduction replaces each variable of a lambda function which is free in the term of that function with the corresponding actual argument:

((lambda (y) (cons ’lemon y)) ’juice) -> (cons ’lemon ’juice)

Y is free in (cons ’lemon y), so it is replaced by the value associated with x.

Things are different here:

((lambda (x) (lambda (x) x)) ’foo) ----> (lambda (x) x)

Because x is bound in (lambda (x) x), it is not replaced with ’foo.

This is a mixed scenario:

((lambda (x) (list x (lambda (x) x))) ’foo) ----> (list ’foo (lambda (x) x))

The first x is free and therefore replaced; the second one is bound in a function and hence left alone.

Beta reduction is a transformation of the lambda calculus (LC). LC is the mathematical foundation of all LISPy languages.

Beta reduction is a formal model for closures and parameter binding inzenlisp function calls.

2.6.2 recursive functions

Here is a simple function:

(define (d x)

(or (atom x) (d (cdr x))))

Whatever you pass to d, it always returns :t:

(d ’x) => :t (d ’#xyz) -> (d ’#yz) -> (d ’#z) -> (d ())

=> :t

Here is a not so simple question:

If lambda closes over all of its free variables and

(define (d x) (or (atom x) (d (cdr x))))

is equivalent to

(define d (lambda (x) (or (atom x) (d (cdr x)))))

then lambda must close over d before d gets bound to the resulting closure.

Then how can d recurse?

Obviously, it can.

The answer is that d is not really a closure at all (although it looks like one).

Whenever define binds a name to a lambda function, it stops lambda from capturing its free variables.

The result is a closure with an empty environment.

Because the function that is bound to d does not have a local value for d, it resorts to the global binding of d.

This method is called dynamic scoping.

It is called ‘‘dynamic’’, because it allows to change the values of free variables dynamically.

Here is dynamic scoping in action:

(define food ’marmelade) (define (get-food) food) (get-food) => ’marmelade (define food ’piece-of-cake) (get-food) => ’piece-of-cake

Because dynamic scoping allows to bind values to symbols after using the symbols in functions, it can even be used to create mutually recursive functions:

(define (d1 x) (or (atom x) (d2 (cdr x)))) (define (d2 x) (or (atom x) (d1 (cdr x)))) (d1 ’#xyz) => :t

Two functions f and g are mutually recursive if f calls g and g calls f .

BTW, you can use let with an empty environment to make define create a closure:

(define food ’marmelade)

(define get-food (let () (lambda () food))) (get-food) => ’marmelade

(define food ’piece-of-cake) (get-food) => ’marmelade

However, this method renders recursive functions impossible:

(define dc (let () (lambda (x) (or (atom x)

(dc (cdr x)))))) (dc ’#xyz) => bottom

2.6.3 recursive closures

Let can bind trivial functions:

(let ((cons-lemon (lambda (x)

(cons ’lemon x)))) (cons-lemon ’cake))

=> ’(lemon . cake)

Let can not bind recursive functions, because the function closes over its own name before it is bound to that name:

(let ((d (lambda (x) (or (atom x)

(d (cdr x)))))) (d ’#xyz))

=> bottom

Letrec can bind recursive functions:

(letrec ((d (lambda (x) (or (atom x)

(d (cdr x)))))) (d ’#xyz))

=> :t

This is why it is called letrec.

Using letrec, functions like reverse [page 21] can be packaged in one single define:

(define (reverse a) (letrec

((reverse2

(lambda (a r)

(cond ((null a) r)

(t (reverse2 (cdr a)

(cons (car a) r))))))) (reverse2 a ())))

Even mutual recursion can be implemented using letrec:

(letrec ((d1 (lambda (x)

(or (atom x) (d2 (cdr x))))) (d2 (lambda (x)

(or (atom x) (d1 (cdr x)))))) (d1 ’#xyz))

=> :t

The only difference between let and letrec is that letrec fixes recursive references in its environment after binding values to symbols.

Therefore

Local functions should be bound by letrec, local data by let.

2.6.4 recursive bindings

Here is an expression reducing to a closure:

(lambda () f)

Applications of the closure reduce to no value, because f has no value:

((lambda () f)) => bottom ; make sure that F is unbound!

Zenlisp has a set of meta functions that modify the state of the interpreter. The meta function ap-plication

(closure-form env)

makes the interpreter display the lexical environments of closures when printing their nor-mal forms:

(lambda () f) => (closure () f ((f . {void})))

The closure prints in round brackets because its full representation is unambiguous.

All four parts of the closure print now: the closure keyword, the argument list, the body, and the lexical environment captured by lambda.

The environment is stored in an association list (a.k.a. alist).

An association list is a list of pairs, where each car part of a pair is a key and each cdr part is a value associated with that key:

((key1 . value1) ... (keyn . valuen))

In lexical environments, variable names are keys and the values associated with the keys are the values of those variables.

The assq and assoc functions are used to retrieve associations from alists:

(define alist ’((food . orange) (drink . milk))) (assq ’drink alist) => ’(drink . milk)

(assq ’soap alist) => :f

Assoc is related to assq in the same way as member is related to memq [page 15]:

(assq ’(key) ’(((key) . value))) => :f

(assoc ’(key) ’(((key) . value))) => ’((key) . value)

The problem with the alist of the closure

(closure () f ((f . {void})))

is that f is associated with no specific value --- even if the closure itself is associated with the symbol f (the form {void} indicates that f is not bound at all):

(let ((f (lambda () f))) f)

=> (closure () f ((f . {void})))

We mean to refer to the inner f , but lambda closes over the outer f .

Letrec uses a function called recursive-bind to fix environments containing recursive ref-erences:

(recursive-bind ’((f . (closure () f ((f . wrong))))))

It does not matter what f is bound to in the inner context, because that is exactly what recursive-bind will change.

In the resulting structure, wrong is replaced with a reference to the value of the outer f :

(recursive-bind ’((fouter . (closure () finner ((finner . wrong))))))

=> ’((fouter . (closure () finner ((finner . fouter)))))

Because f now contains a reference to f , it is a cyclic structure.

When you reduce above application of recursive-bind with closure-form set to env, the interpreter will attempt to print an infinite structure:

(recursive-bind ’((f . (closure () f ((f . wrong))))))

=> ’((f . (closure () f

((f . (closure () f

((f . (closure () f ((f . ...

This is why only the argument lists of closures print by default. You can restore this behaviour by applying

(closure-form args)

In document Zen Style Programming (Page 35-42)

Related documents