プログラムは異なる場所で同じことをしなければならないことがよくあります。全てのステートメントを毎回繰り返すのは面倒で間違いの元でもあります。それらを一箇所に置き、必要な時にプログラムが迂回できたら便利です。関数が考案された目的はこれです: 関数は缶詰にしたコードで、プログラムは好きな時に迂回してそこに行くことができます。文字列を画面に表示するには相当数のステートメントが必要ですが、print 関数を使うと print("Aleph") と書くだけで済みます。
関数を缶詰にしたコードと見るのは正当な評価ではありません。必要とあれば関数は純関数にもアルゴリズムにもなれ、間接参照、抽象化、意思決定、モジュール、継続、データ構造その他の役割を担うこともできます。関数を効果的に使えることは本格的なプログラミングに必須のスキルです。本章は関数への導入で、6 章 で関数の巧妙さについてさらに詳しく説明します。
まず、純関数とは数学の授業で関数と呼ばれていたもので、誰でも学生時代に経験したことがあると思います。コサインや絶対値を求めるのは引数が 1 つの純関数です。加算は引数が 2 つの純関数です。
純関数の最大の特徴は、同じ引数を与えると必ず同じものを返し、副作用は全くありません。引数に応じて値を返し、他のものをいじくり回すことはありません。
JavaScript では加算は演算子ですが、次のように関数でラップすることができます (本ドキュメントの何処とは言えませんが、実際に役立つ場面を見掛けることでしょう):
function add(a, b) {
return a + b;
}
show(add(2, 2));
add は関数の名前です。a と b は引数の名前で、return a + b; は関数の本体です。
新しい関数を作る時には必ずキーワード function が使われます。その後に名前が続く場合、関数はその名前で保存されます。名前の後に一連の引数名が続き、最後に関数本体が来ます。while ループや if ステートメントと異なり、関数本体は中括弧で囲まなければなりません 1。
キーワード return の後に続く式は関数が返す値を決定します。制御が return ステートメントに来ると、現在の関数からジャンプし、返された値をその関数を呼び出したコードに渡します。return ステートメントの後に式が続かない場合、関数は undefined を返します。
勿論、本体は複数のステートメントを含むことができます。次の関数は (正の整数の) 累乗を計算します:
function power(base, exponent) {
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
}
show(power(2, 10));
exercise 2.2 を解いたら、累乗を計算するテクニックはよくご存知だと思います。
変数 (result) の作成とその更新は副作用です。純関数には副作用が無いと言いませんでしたっけ?
関数内で作られた変数は関数内でのみ存在します。これはプログラマーにとっては好都合で、そうでなければプログラマーは変数が必要になる度に異なる名前を考えなければならなくなります。result は power 内でのみ存在し、それへの変更は関数が返すまでしか続きません。それを呼び出したコードから観ると、副作用は全く存在しません。
Ex. 3.1
引数として与えられる数の絶対値を返す関数 absolute を書いてください。負数の絶対値は与えられた数の正数版で、正数 (またはゼロ) の絶対値がその数自身です。
function absolute(number) {
if (number < 0)
return -number;
else
return number;
}
show(absolute(-144));
純関数には 2 つの素晴らしい特徴があります。どんな純関数かを思い浮かべることが容易なことと、その再利用が簡単なことです。
純関数の場合、その呼び出しはそれ自身の中で行われます。正しく機能しているか不安な時は、コンソールから直接それを呼び出してテストすることができます。コンテキスト 2 に依存しないため、シンプルです。関数をテストするプログラムを書いて、それを自動化することは簡単です。非純関数は色々な要因によって異なる値を返し、考えるのもテストするのも難しい副作用があります。
純関数は自足的で、非純関数よりも広い範囲で役に立ちそうです。例えば show を例にとってみましょう。この関数の有益性はスクリーン上に表示できる場所が存在するかどうかで決まります。表示できる場所が無ければ、この関数は役に立ちません。関連する関数、format とでも呼んでおきましょう。これは引数を 1 つ取り、引数の値を表す文字列を返します。この関数は show よりも多くの場合に役立ちます。
勿論 format は show の問題の解決にはならず、いかなる純関数もその問題を解決できません。何故なら、副作用が必要だからです。多くの場合、我々が必要とするのは非純関数です。その他の場合は純関数でも問題は解決しますが、非純変数がずっと便利で効果的です。
このように、純関数で簡単に表現できる場合はその方法で書くべきです。しかし、非純関数を書くことが悪いことだとは考えないでください。
副作用を持つ関数に return ステートメントを含める必要はありません。return ステートメントがなければ、その関数は undefined を返します。
function yell(message) {
alert(message + "!!");
}
yell("Yow");
関数の引数名は関数内の変数として使うことができます。引数名は呼び出された関数の引数の値を参照し、通常の変数と同様、関数内で作成され、関数外では存在しません。最上位レベルの環境とは別に、小さなローカル環境が関数呼び出しによって作られます。関数内の変数を調べる際、まずローカル環境がチェックされ、そこに変数が無い場合に最上位レベルの環境がチェックされます。これにより、関数内の変数によって同じ名前を持つ最上位変数を '隠す (shadow)' ことが可能になります。
function alertIsPrint(value) {
var alert = print;
alert(value);
}
alertIsPrint("Troglodites");
このローカル環境の変数は関数内のコードにのみ可視的で、この関数が別の関数を呼び出すと、呼び出された関数には最初の関数内の変数が見えません:
var variable = "top-level";
function printVariable() {
print("inside printVariable, the variable holds '" +
variable + "'.");
}
function test() {
var variable = "local";
print("inside test, the variable holds '" + variable + "'.");
printVariable();
}
test();
これは捕らえにくい現象ですが、実に有益な事実です。関数が他の関数の 中で 定義されると、そのローカル環境は最上位環境ではなく、それを取り巻くローカル環境を基に作られます。
var variable = "top-level";
function parentFunction() {
var variable = "local";
function childFunction() {
print(variable);
}
childFunction();
}
parentFunction();
これが帰着するのは、どの変数が関数内で可視的かはその関数のプログラム・テキスト内での場所によって決まるということです。関数定義の '上部' で定義された変数は全て可視的です。つまり、それを取り巻く関数本体の中の変数も、プログラムの最上位にある変数も、共に可視的です。変数の可視性に関するこのアプローチは レキシカル・スコーピングと呼ばれます。
他のプログラミング言語の経験のある方は、(中括弧で囲まれた) コード・ブロックも新しいローカル環境を作るのではないかと期待されるかもしれません。JavaScript では違います。関数が唯一新しいスコープを作成します。次のような独立したブロックを使うことができます...
var something = 1;
{
var something = 2;
print("Inside: " + something);
}
print("Outside: " + something);
... が、ブロック内の something はブロック外にある同じ変数を参照します。事実、このようなブロックが可能でも全く無意味です。これが JavaScript 開発者達の重大なデザイン・ミスであることは多くの人々が認めています。ECMAScript 4 ではブロック内にある変数の定義について何らかの方法が追加されることになっています。
驚くような例があります:
var variable = "top-level";
function parentFunction() {
var variable = "local";
function childFunction() {
print(variable);
}
return childFunction;
}
var child = parentFunction();
child();
parentFunction はその内部関数を 返し、最後のコードでこの関数が呼び出されます。parentFunction がこの時点で実行を終了しても、variable が値 "local" を持つローカル環境は依然存在し、childFunction はまだそれを使っているのです。この現象を クロージャ (閉包)と呼びます。