extract closures discussion to separate page
[lambda.git] / topics / closures.mdwn
1 ## Evaluation in the untyped lambda calculus: environments
2
3 The previous interpreter strategy is nice because it corresponds so closely to the
4 reduction rules we give when specifying our lambda calculus. (Including
5 specifying evaluation order, which redexes it's allowed to reduce, and
6 so on.) But keeping track of free and bound variables, computing fresh
7 variables when needed, that's all a pain.
8
9 Here's a better strategy. Instead of keeping all of the information
10 about which variables have been bound or are still free implicitly
11 inside of the terms, we'll keep a separate scorecard, which we will call an "environment".  This is a
12 familiar strategy for philosophers of language and for linguists,
13 since it amounts to evaluating terms relative to an assignment
14 function. The difference between the substitute-and-repeat approach
15 above, and this approach, is one huge step towards monads.
16
17 The skeleton code for this is at the [[same link as before|/code/untyped_evaluator.ml]].
18 This part of the exercise is the "VB" part of that code.
19
20 You'll see that in the `eval` function, a new kind of value `Closure (ident) (term) (env)`
21 is used. What's that about?
22
23 The `Closure`s are for handling the binding of terms that have locally free variables in them. Let's
24 see how this works. For exposition, I'll pretend that the code you're working with has
25 primitive numbers in it, though it doesn't. (But the fuller code it's simplified from does; see below.)
26 Now consider:
27
28     term                             environment
29     ----                             -----------
30     (\w.(\y.y) w) 2                  []
31     (\y.y) w                         [w->2]
32     y                                [y->w, w->2]
33
34 In the first step, we bind `w` to the term `2`, by saving this association in our environment. In the second step, we bind `y` to the term `w`. In the third step, we would like to replace `y` with whatever its current value is according to our scorecard/environment. But a naive handling of this would replace `y` with `w`; and that's not the right result, because `w` should itself be mapped onto `2`. On the other hand, in:
35
36     term                             environment
37     ----                             -----------
38     (\x w. x) w 2                    []
39     (\w. x) 2                        [x->w]
40     x                                [w->2, x->w]
41
42 Now our final term _should_ be replaced with `w`, not with `2`. So evidently it's not so simple as just recursively re-looking up variables in the environment.
43
44 A good strategy for handling these cases would be not to bind `y` to the term `w`, but rather to bind it to _what the term `w` then fully evaluates to_. Then in the first case we'd get:
45
46 <pre>
47 term                             environment
48 ----                             -----------
49 (\w.(\y.y) w) 2                  []
50 (\y.y) w                         [w->2]
51 y                                [<b>y->2</b>, w->2]
52 </pre>
53
54 And at the next step, `y` will evaluate directly to `2`, as desired. In the other example, though, `x` gets bound to `w` when `w` is already free. (In fact the code skeleton we gave you won't permit that to happen; it refuses to perform any applications except when the arguments are "result values", and it doesn't count free variables as such. As a result, other variables can never get bound to free variables.)
55
56 So far, so good.
57
58 But now consider the term:
59
60     (\f. x) ((\x y. y x) 0)
61
62 Since the outermost head `(\f. x)` is already a `Lambda`, we begin by evaluating the argument `((\x y. y x) 0)`. This results in:
63
64     term                             environment
65     ----                             -----------
66     (\f. x) (\y. y x)                [x->0]
67
68 But that's not right, since it will result in the variable `x` in the head also being associated with the argument `0`. Instead, we want the binding of `x` to `0` to be local to the argument term `(\y. y x)`. For the moment, let's notate that like this:
69
70 <pre>
71 term                             environment
72 ----                             -----------
73 (\f. x)<sub>1</sub> (\y. y x)<sub>2</sub>              []<sub>1</sub>   [x->0]<sub>2</sub>
74 </pre>
75
76 Now, let's suppose the head is more complex, like so:
77
78     (\f. (\x. f x) I) ((\x y. y x) 0)
79
80 That might be easier to understand if you transform it to:
81
82     let f = (let x = 0 in \y. y x) in
83     let x = I in
84     f x
85
86 Since the outermost head `(\f. (\x. f x) I)` is already a `Lambda`, we begin by evaluating the argument `((\x y. y x) 0)`. This results in:
87
88 <pre>
89 term                             environment
90 ----                             -----------
91 (\f. (\x. f x) I)<sub>1</sub> (\y. y x)<sub>2</sub>    []<sub>1</sub> [x->0]<sub>2</sub>
92 </pre>
93
94 Now the argument is not itself any longer an `App`, and so we are ready to evaluate the outermost application, binding `f` to the argument term. So we get:
95
96 <pre>
97 term                             environment
98 ----                             -----------
99 ((\x. f x) I)<sub>1</sub>                   [f->(\y. y x)<sub>2</sub>]<sub>1</sub> [x->0]<sub>2</sub>
100 </pre>
101
102 Next we bind `x` to the argument `I`, getting:
103
104 <pre>
105 term                             environment
106 ----                             -----------
107 (f x)<sub>1</sub>                           [x->I, f->(\y. y x)<sub>2</sub>]<sub>1</sub> [x->0]<sub>2</sub>
108 </pre>
109
110 Now we have to apply the value that `f` is bound to to the value that `x` is bound to. But notice there is a free variable `x` in the term that `f` is bound to. How should we interpret that term? Should the evaluation proceed:
111
112 <pre>
113 (\y. y <b>x<sub>1</sub></b>) x<sub>1</sub>                    [x->I, ...]<sub>1</sub> [x->0]<sub>2</sub>
114 y <b>x<sub>1</sub></b>                             [y->I, x->I, ...]<sub>1</sub> [x->0]<sub>2</sub>
115 I I
116 I
117 </pre>
118
119 using the value that `x` is bound to in context<sub>1</sub>, _where the `f` value is applied_? Or should it proceed:
120
121 <pre>
122 (\y. y <b>x<sub>2</sub></b>) x<sub>1</sub>                    [x->I, ...]<sub>1</sub> [x->0]<sub>2</sub>
123 y <b>x<sub>2</sub></b>                             [y->I, x->I, ...]<sub>1</sub> [x->0]<sub>2</sub>
124 I 0
125 0
126 </pre>
127
128 using the value that `x` was bound to in context<sub>2</sub>, _where `f` was bound_?
129
130 In fact, when we specified rules for the Lambda Calculus, we committed ourselves to taking the second of these strategies. But both of the kinds of binding described here are perfectly coherent. The first is called "dynamic binding" or "dynamic scoping" and the second is called "lexical or static binding/scoping". Neither is intrinsically more correct or appropriate than the other. The first is somewhat easier for the people who write implementations of programming languages to handle; so historically it used to predominate. But the second is easier for programmers to reason about. In Scheme, variables are bound in the lexical/static way by default, just as in the Lambda Calculus; but there is special vocabulary for dealing with dynamic binding too, which is useful in some situations. (As far as I'm aware, Haskell and OCaml only provide the lexical/static binding.)
131
132 In any case, if we're going to have the same semantics as the untyped Lambda Calculus, we're going to have to make sure that when we bind the variable `f` to the value `\y. y x`, that (locally) free variable `x` remains associated with the value `2` that `x` was bound to in the context where `f` is bound, not the possibly different value that `x` may be bound to later, when `f` is applied. One thing we might consider doing is _evaluating the body_ of the abstract that we want to bind `f` to, using the then-current environment to evaluate the variables that the abstract doesn't itself bind. But that's not a good idea. What if the body of that abstract never terminates? The whole program might be OK, because it might never go on to apply `f`. But we'll be stuck trying to evaluate `f`'s body anyway, and will never get to the rest of the program. Another thing we could consider doing is to substitute the `2` in for the variable `x`, and then bind `f` to `\y. y 2`. That would work, but the whole point of this evaluation strategy is to avoid doing those complicated (and inefficient) substitutions. Can you think of a third idea?
133
134 What we will do is have our environment associate `f` not just with a `Lambda` term, but also with the environment that was in place _when_ `f` was bound. Then later, if we ever do use `f`, we have that saved environment around to look up any free variables in. More specifically, we'll associate `f` not with the _term_:
135
136     Lambda("y", BODY)
137
138 but instead with the `Closure` structure:
139
140     Closure("y", BODY, SAVED_ENV)
141
142 where `BODY` is the term that constituted the body of the corresponding `Lambda`, and `SAVED_ENV` is the environment in place when `f` is being bound. (In this simple implementation, we will just save the _whole environment_ then in place. But in a more efficient implementation, we'd sift through it and only keep those bindings for variables that are free in `BODY`. That would take up less space.)
143
144 So that's what's going on with those `Closure`s. In the simple code we gave you to work with, we just made these another clause in the `term` datatype. That's really not correct. `Closure`s aren't terms. The syntax for our language doesn't have any constituents that get parsed into `Closure`s. `Closure`s are only created *during the course of evaluating terms*: specifically, when a variable gets bound to an abstract, which may itself contain variables that are locally free (not bound by the abstract itself). So really we should have two datatypes, one for terms, and another for the *results* (sometimes called "values") that terms can evaluate to. `Closure`s are results, but they aren't terms. `App`s are terms, but not results. If we had primitive numbers or other constants in our language, they might be both terms and results. In the fuller code from which your homework is simplified, this is how the types are in fact defined. But it makes things more complicated. So to keep things simple for the homework, we just pretended like `Closure`s were a new, exotic kind of `term`.
145
146 In any case, now you know what's going on with the `Closure`s, and you should be able to complete the missing pieces of the `eval` function in the code skeleton linked above.
147
148 If you've completed all the missing parts correctly (there are six gaps for the previous stage of the homework, and two gaps for this stage), then you should be able to compile the code skeleton, and use it as described in the comments at the start of the code.