Definitions Declarations - SICP Comparison Edition" />
Original | JavaScript | |
Our environment model of evaluation and our metacircular evaluator execute definitions in sequence, extending the environment frame one definition at a time. This is particularly convenient for interactive program development, in which the programmer needs to freely mix the application of procedures definition procedures. However, if we think carefully about the internal definitions used to implement block structure (introduced in section 1.1.8), we will find that name-by-name extension of the environment may not be the best way to define local variables. Consider a procedure with internal definitions, such as (define (f x) (define (even? n) (if (= n 0) true (odd? (- n 1)))) (define (odd? n) (if (= n 0) false (even? (- n 1)))) $\langle$ rest of body of$\rangle$ f) Our intention here is that the name odd? in the body of the procedure even? should refer to the procedure odd? that is defined after even?. The scope of the name odd? is the entire body of f, not just the portion of the body of f starting at the point where the define for odd? occurs. Indeed, when we consider that odd? is itself defined in terms of even?—so that even? and odd? are mutually recursive procedures—we see that the only satisfactory interpretation of the two defines is to regard them as if the names even? and odd? were being added to the environment simultaneously. More generally, in block structure, the scope of a local name is the entire procedure body in which the define is evaluated.
As it happens, our interpreter will evaluate calls to
f correctly, but for an
There is, however, a simple way to treat definitions
so that internally defined names have truly simultaneous
scope—just create all local variables that will be in the
current environment before evaluating any of the value expressions.
One way to do this is by a syntax transformation on
lambda expressions. Before evaluating the
body of a lambda expression, we
|
In JavaScript, the scope of a declaration is the entire block that immediately surrounds the declaration, not just the portion of the block starting at the point where the declaration occurs. This section takes a closer look at this design choice. Let us revisit the pair of mutually recursive functions is_even and is_odd from Section 3.2.4, declared locally in the body of a function f. function f(x) { function is_even(n) { return n === 0 ? true : is_odd(n - 1); } function is_odd(n) { return n === 0 ? false : is_even(n - 1); } return is_even(x); } Our intention here is that the name is_odd in the body of the function is_even should refer to the function is_odd that is declared after is_even. The scope of the name is_odd is the entire body block of f, not just the portion of the body of f starting at the point where the declaration of is_odd occurs. Indeed, when we consider that is_odd is itself defined in terms of is_even—so that is_even and is_odd are mutually recursive functions—we see that the only satisfactory interpretation of the two declarations is to regard them as if the names is_even and is_odd were being added to the environment simultaneously. More generally, in block structure, the scope of a local name is the entire block in which the declaration is evaluated. The evaluation of blocks in the metacircular evaluator of section 4.1.1 achieves such a simultaneous scope for local names by scanning out the declarations in the block and extending the current environment with a frame containing bindings for all the declared names before evaluating the declarations. Thus the new environment in which the block body is evaluated already contains bindings for is_even and is_odd, and any occurrence of one of these names refers to the correct binding. Once their declarations are evaluated, these names are bound to their declared values, namely function objects that have the extended environment as their environment part. Thus, for example, by the time is_even gets applied in the body of f, its environment already contains the correct binding for the symbol is_odd, and the evaluation of the name is_odd in the body of is_even retrieves the correct value.
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
Why can't the evaluator take care of this chore, and hoist all function declarations to the beginning of the block in which they appear? Function declarations outside of blocks should be hoisted to the beginning of the program.
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
Original | JavaScript | |
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
simultaneousscope rule for internal definitions without constructing the extra frame. There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
Original | JavaScript | |
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
The design of our evaluator of section 4.1.1 imposes a runtime burden on the evaluation of blocks: It needs to scan the body of the block for locally declared names, extend the current environment with a new frame that binds those names, and evaluate the block body in this extended environment. Alternatively, the evaluation of a block could extend the current environment with an empty frame. The evaluation of each declaration in the block body would then add a new binding to that frame. To implement this design, we first simplify eval_block: function eval_block(component, env) { const body = block_body(component); return evaluate(body, extend_environment(null, null, env); } The function eval_declaration can no longer assume that the environment already has a binding for the name. Instead of using assign_symbol_value to change an existing binding, it calls a new function, add_binding_to_frame, to add to the first frame of the environment a binding of the name to the value of the value expression. function eval_declaration(component, env) { add_binding_to_frame( declaration_symbol(component), evaluate(declaration_value_expression(component), env), first_frame(env)); return undefined; } function add_binding_to_frame(symbol, value, frame) { set_head(frame, pair(symbol, head(frame))); set_tail(frame, pair(value, tail(frame))); } |
Original | JavaScript | |
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
Original | JavaScript | |
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
With sequential declaration processing, the scope of a
declaration is no longer the entire block that immediately surrounds
the declaration, but rather just the portion of the block starting at
the point where the declaration occurs.
Although we no longer have simultaneous scope, sequential
declaration processing
will evaluate calls to the function
f at the beginning of this section
correctly, but for an
Sequential declaration processing is more efficient and easier to implement than scanning out names. However, with sequential processing, the declaration to which a name refers may depend on the order in which the statements in a block are evaluated. In exercise 4.29, we see that views may differ as to whether that is desirable. There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
Original | JavaScript | |
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
|
management is not responsibleremark in footnote 4 of chapter 1. By insisting that internal definitions come first and do not use each other while the definitions are being evaluated, the IEEE standard for Scheme leaves implementors some choice in the mechanism used to evaluate these definitions. The choice of one evaluation rule rather than another here may seem like a small issue, affecting only the interpretation of
badly formedprograms. However, we will see in section 5.5.6 that moving to a model of simultaneous scoping for internal definitions avoids some nasty difficulties that would otherwise arise in implementing a compiler.
pure $\lambda$-calculusimplementation of recursion. (See
pure $\lambda$-calculusimplementation of recursion. (See