In using sum as in
section 1.3.1, it seems
terribly awkward to have to declare trivial functions such as
pi_term and
pi_next just so we can use them as
arguments to our higher-order function. Rather than declare
pi_next and
pi_term, it would be more convenient
to have a way to directly specify the function that returns its
input incremented by 4 and the function that returns the
reciprocal of its input times its input plus 2. We can do this
by introducing the lambda expression as a syntactic form for
creating functions.
Using lambda expressions, we can describe what we want as
1.3.1節のように sum を使う際、高階関数の引数として使うためだけに pi_term や pi_next のような些細な関数を宣言しなければならないのは、非常に厄介です。pi_next や pi_term を宣言するよりも、入力を4だけ増やして返す関数や入力と入力+2の積の逆数を返す関数を直接指定できる方が便利でしょう。関数を作るための構文形式としてラムダ式を導入することで、これが可能になります。ラムダ式を使えば、欲しいものを次のように記述できます。
x => x + 4
and
x => 1 / (x * (x + 2))
Then we can express our
pi_sum
function
without
declaring any auxiliary functions:
これにより、補助
関数を宣言
することなく
pi_sum 関数
を表現できます:
function pi_sum(a, b) {
return sum(x => 1 / (x * (x + 2)),
a,
x => x + 4,
b);
}
Again using
a lambda expression,
we can write the integral
function
without having to
declare the auxiliary function
add_dx:
function integral(f, a, b, dx) {
return sum(f,
a + dx / 2,
x => x + dx,
b)
*
dx;
}
In general, lambda expressions are used to create functions in the
same way as function declarations,
except that no name is specified for the function and the
return keyword and braces are omitted
(if there is only one
parameter, the
parentheses around the parameter list can also be
omitted, as in the examples we have
seen).
The resulting function is just as much a function
as one that is created using a function declaration statement.
The only difference is that it has not been associated with any name in the
environment.
Another use of
lambda expressions is in creating local names.
We often need local names in our functions
other than those that have been bound as parameters.
For example, suppose we wish to compute the function
\[\begin{array}{lll}
f(x, y)&=&x(1 + x y)^2 +y (1 - y) + (1 + x y)(1 - y)
\end{array}\]
which we could also express as
これは次のようにも表現できます。
\[\begin{array}{rll}
a &=& 1+xy\\
b &=& 1-y\\
f(x, y) &= &x a^2 +y b + a b
\end{array}\]
In writing a
function
to compute $f$, we would like to include as
local names
not only $x$ and $y$
but also the names of intermediate quantities like
$a$ and $b$. One way
to accomplish this is to use an auxiliary
function to bind the local names:
function f(x, y) {
function f_helper(a, b) {
return x * square(a) + y * b + a * b;
}
return f_helper(1 + x * y, 1 - y);
}
Of course, we could use a
lambda
expression to specify an anonymous
function for binding our local names.
The
function body
then becomes a single call to that
function:
function f_2(x, y) {
return ( (a, b) => x * square(a) + y * b + a * b
)(1 + x * y, 1 - y);
}
A more convenient way to declare local names is by using constant
declarations within the body of the function. Using
const, the function
can be written as
We have seen that it is often useful to declare names that are local to
function declarations. When functions become big, we should
keep the scope of the names as narrow as possible.
Consider for example expmod in
exercise 1.26.
function expmod(base, exp, m) {
return exp === 0
? 1
: is_even(exp)
? ( expmod(base, exp / 2, m)
* expmod(base, exp / 2, m)) % m
: (base * expmod(base, exp - 1, m)) % m;
}
This function is unnecessarily inefficient, because it contains two
identical calls:
この関数は不必要に非効率です。なぜなら、次の同一の呼び出しが2つ含まれているからです:
expmod(base, exp / 2, m);
While this can be easily fixed in this example using the
square function, this is not so easy
in general. Without using square,
we would be tempted to introduce a local name for the expression as
follows:
function expmod(base, exp, m) {
const half_exp = expmod(base, exp / 2, m);
return exp === 0
? 1
: is_even(exp)
? (half_exp * half_exp) % m
: (base * expmod(base, exp - 1, m)) % m;
}
This would make the function not just inefficient, but actually
nonterminating! The problem is that the constant declaration appears
outside the conditional expression, which means that it is executed even
when the base case exp === 0 is met.
To avoid this situation, we provide for
conditional statements, and allow return
statements to appear in the branches of the statement. Using a
conditional statement, we can write the function
expmod as follows:
function expmod(base, exp, m) {
if (exp === 0) {
return 1;
} else {
if (is_even(exp)) {
const half_exp = expmod(base, exp / 2, m);
return (half_exp * half_exp) % m;
} else {
return (base * expmod(base, exp - 1, m)) % m;
}
}
}
The general form of a conditional statement is
条件文の一般的な形式は次の通りです。
if ($predicate$) { $consequent$-$statements$ } else { $alternative$-$statements$ }
As for a conditional expression, the interpreter first evaluates the
$predicate$. If it evaluates to true,
the interpreter evaluates the
$consequent$-$statements$ in sequence, and if it
evaluates to false, the interpreter evaluates
the $alternative$-$statements$ in sequence. Evaluation of a return
statement returns from the surrounding function, ignoring any
statements in the sequence
after the return statement and any statements after the conditional statement.
Note that any constant declarations occurring in either part are local to that
part, because each part is enclosed in braces and thus forms its own
block.
In section 2.2.4, we will
extend the syntax of
lambda expressions to allow a block as the body rather than just
an expression, as in function declaration
statements.
In
JavaScript, there are subtle differences between the two versions:
A function declaration statement is
automatically ``hoisted'' (moved) to the beginning of the surrounding
block or to the beginning of the program if it occurs
outside of any block, whereas a constant declaration is not moved.
Names declared with function declaration can be reassigned using
assignment (section 3.1.1), whereas
names declared with constant declarations can't. In this
book, we avoid these features and treat a
function declaration as equivalent to the corresponding constant
declaration.
It would be clearer and less intimidating to people learning
JavaScript
if a
term
more obvious than
lambda expression, such as function definition,
were used. But the convention is
very firmly entrenched, not just for Lisp and Scheme but also for
JavaScript, Java and other languages, no doubt partly due to the
influence of the Scheme editions of this book.
The notation is adopted from the
$\lambda$ calculus, a
mathematical formalism introduced by the mathematical logician
Alonzo Church (1941). Church developed the
$\lambda$ calculus to provide a rigorous
foundation for studying the notions of
function and function application. The
$\lambda$ calculus has become a basic
tool for mathematical investigations of the
semantics of programming languages.
Note that a name declared in a block cannot be used before the
declaration is fully evaluated, regardless of whether the same name is
declared outside the block. Thus in the program below, the
attempt to use the a declared
at the top level
to provide a value for the calculation of
the b declared in
f cannot work.
ブロック内で宣言された名前は、同じ名前がブロックの外で宣言されているかどうかに関わらず、宣言が完全に評価される前に使うことはできないことに注意してください。したがって、次のプログラムでは、f 内で宣言された b の計算に値を提供するために、トップレベルで宣言された a を使おうとしてもうまくいきません。
const a = 1;
function f(x) {
const b = a + x;
const a = 5;
return a + b;
}
f(10);
The program
leads to an error, because the a in
a + x is used before its declaration
is evaluated. We will return to this program in
section 4.1.6
(exercise 4.19), after we learn
more about evaluation.
このプログラムはエラーになります。なぜなら、a + x の中の a がその宣言が評価される前に使われるからです。評価についてもっと学んだ後、 4.1.6節(演習問題 4.19)でこのプログラムに戻ります。
The substitution
model can be expanded to say that for a constant declaration, the value of the
expression after =
is substituted for the name before
=
in the rest of the block body (after the declaration), similar to the
substitution of arguments for parameters in the evaluation of a
function application.