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.
function f_3(x, y) {
const a = 1 + x * y;
const b = 1 - y;
return x * square(a) + y * b + a * b;
}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.
(n => (fact => fact(fact, n))
((ft, k) => k === 1
? 1
: k * ft(ft, k - 1)))(10);
// solution provided by GitHub user LucasGdosR
// The Fibonacci function receives n as an argument
// It applies the fib function recursively, passing n as an argument,
// as well as the initial arguments (k = 1, fib1 = 1, fib2 = 1)
(n => (fib => fib(fib, n, 2, 1, 1))
// The fib function is then defined as ft,
// with parameters n, k, fib1, and fib2
// Establish the base cases: n === 1 or n === 2
((ft, n, k, fib1, fib2) => n === 1
? 1
: n === 2
? 1
:
// Iterate until k equals n. Notice k starts at 2, and gets incremented every iteration
k === n
// When k reaches n, return the accumulated fib2
? fib2
// Otherwise, accumulate the sum as the new fib2
: ft(ft, n, k + 1, fib2, fib1 + fib2)));
// solution provided by GitHub user LucasGdosR
function f(x) {
return ((is_even, is_odd) => is_even(is_even, is_odd, x))
((is_ev, is_od, n) => n === 0 ? true : is_od(is_ev, is_od, n - 1),
(is_ev, is_od, n) => n === 0 ? false : is_ev(is_ev, is_od, n - 1));
}
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);
}
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)));
}
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
accidental
reason: Since the declarations
of the internal functions come first, no calls to these functions
will be evaluated until all of them have been declared. Hence,
is_odd will have been declared by the time
is_even is executed. In fact,
sequential declaration processing
will give the same result as our scanning-out-names evaluator in
section 4.1.1
for any function
in which the
internal declarations come first in a body and evaluation of the value
expressions for the declared names doesn't actually use any of
the declared names.
Exercise 4.19 shows
an example of a function that doesn't
obey these restrictions, so that the alternative evaluator isn't
equivalent to our scanning-out-names evaluator.
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.19, we see that views may differ as to whether that is desirable.
const a = 1;
function f(x) {
const b = a + x;
const a = 5;
return a + b;
}
f(10);
pure $\lambda$-calculusimplementation of recursion. (See