Exercise 4.19. Ben Bitdiddle, Alyssa P. Hacker, and Eva Lu Ator are arguing about the desired result of evaluating the expression (let ((a 1)) (define (f x) (define b (+ a x)) (define a 5) (+ a b)) (f 10)) Ben asserts that the result should be obtained using the sequential rule for define: b is defined to be 11, then a is defined to be 5, so the result is 16. Alyssa objects that mutual recursion requires the simultaneous scope rule for internal procedure definitions, and that it is unreasonable to treat procedure names differently from other names. Thus, she argues for the mechanism implemented in exercise 4.16. This would lead to a being unassigned at the time that the value for b is to be computed. Hence, in Alyssa's view the procedure should produce an error. Eva has a third opinion. She says that if the definitions of a and b are truly meant to be simultaneous, then the value 5 for a should be used in evaluating b. Hence, in Eva's view a should be 5, b should be 15, and the result should be 20. Which (if any) of these viewpoints do you support? Can you devise a way to implement internal definitions so that they behave as Eva prefers? ———————————————————————————————————————————————————————————————————————— Ben's opinion seems reasonable, but unlikely to be what the programmer intended. Alyssa's position seems quite reasonable and has the benefit of being easy to implement. Eva's position seems to be the most theoretically elegant, and the least obvious to implement. Eva's intended semantics can, however, be implemented by an interpreter. First, for each definition in the set, create a structure to represent the pending binding. We could for example represent these as a list of the symbol 'pending followed the (as yet unevaluated) expression. The definition of b above then would give '(pending (+ a x)). Each of these structures must be stored in a binding context, similar to the environment frames used for ordinary variables in the language. Next we define a slightly different procedure for variable lookup. This procedure looks in this simultaneous binding context just created as described above before looking anywhere else. If a binding is found there, then the value will be a list, either of the form '(pending ) or '(forced ). If it is of the first form, it is forced as described below, and the value returned. Otherwise, the value is already know and is returned directly. If a binding is not found in this special binding context, then the ordinary environment frames are consulted as they would be for any ordinary variable. Once the simultaneous binding context is set up as described above, we then force each definition in turn. For each definition, we simply evaluate the defining expression, using the special variable lookup procedure described above. (This may cause other definitions to be forced.) The value of this expression is then used to construct a '(forced ) list, with which we replace the '(pending ) list in the special binding context. If there are circular definitions, the interpreter will not terminate, but this can be easily caught by storing a special value like the '*unassigned* used earlier in each position in the special binding table while we are evaluating the corresponding definition.