■1.
What's a Closure?Closure とは何だろうか、どのように動作するんだろうか?と思ったことはありませんか?
いまから、この一歩先行くプログラミングコンセプトを、Javascriptを使ったインタラクティブなレッスンを通して勉強しましょう!
※訳注:インタラクティブな部分は、本家サイトでお楽しみください。ここでは翻訳のみです。(´・ω・`)
さあスタートです!
---
ここにサンプルのJavaScriptコードがあります。カワイイでしょ?
var f = function(x) {
return x + 1;
}
心配しないで。ここにとどまってカワイイコードを見てるだけなんてしないよ。
コードを書いていくよ!
■変数(Variables)
このコードは、x という変数を宣言して、5を代入しているところです。
var x;
x = 5;
●やってみよう
y という変数を宣言して、7を代入してみてください。
書けたら、"submit" ボタンを押してみてね!
--
■2.
Variables and Values (変数と値)
入力を減らすために、変数宣言と代入を一行で書くこともできます。
ここでは、fish という変数を宣言して42を代入しています。
var fish = 42;
JavaScript は数値以外にも沢山の値が扱えます。これは、幾つかの例です。
//String
var txt = "Hello, world!";
//Fancy number
var delta = 0.000157;
//String の配列
var people = ['Jack', 'Jill', 'Bob'];
念のため書いておくと、文字列はシングルクォートかダブルクォートのどちらかを使って囲むことができます。
どちらを使うかは、文字列の中でクォートを扱いたいかどうかで判断すると良いでしょう。
●やってみよう
colors という変数を定義して、3つの色の名前の文字列を持った配列を代入してください。
配列の最初の値は、"blue" その次が"red"。三番目は想像してみてください。☆(ゝω・)vキャピ
■3.
Defining Functions (関数定義)
よし。JavaScriptの変数と値に詳しくなったところで、今度は関数をみてみよう。
ここに、plusOne という一つの引数を取る関数定義があります。
var plusOne = function(x) {
return x + 1;
}
次のコードは、この関数を使って y に値を代入しています。
var y = plusOne(10);
変数 y の値は11になるよね。10+1 が11 だもんね。
関数は一つ以上の引数を取ることもできます。次のコードは、二つの引数を取る関数です。
var area = function(w, h) {
return w * h;
}
●やってみよう
add という名前の二つの引数 x と y を取る関数を定義してください。
関数は、x と y の合計を返します。
■4.
Side Effects (副作用/予想外の影響)
関数は、値を返す以上のこともできます。
それらは、副作用(Side effects)も伴います。
※副作用(Side effects)はこの練習では、睡眠不足で、いやな汗をかいて、不自然に高揚する感覚とかも含んでるよ。☆(ゝω・)vキャピ
このコードをみてください。
var cow, x
cow = "sleepy";
var double = function(a) {
cow = "purple";
return a * 2;
}
x = double(6);
//このとき、cowの値は?
何が起こるでしょう?
変数 cow には "sleepy" が代入され、そして x には12が代入されます。
しかし、突然どこからともなく cow には "purple" が代入されます。
その部分は、double関数の中にあります。
関数は、引数で渡された値を二倍にしますが、そこに副作用として関数の外にある cow に"purple"を代入する副作用があります。
これは、一般の数学での関数と異なります。数学では、関数のアウトプットはインプットで決まります。
それらは double関数のような魔法のスーパー牛パワーを持ってないからね。☆(ゝω・)vキャピ
●やってみよう
addOne という引数に1を足して返すだけの単純な関数を定義してみましょう。
しかし、こっそり cow に"hamburger"をセットしてください。
■5.
Functions are Values(関数だって値)
JavaScriptでは、関数だって値です。5が値で変数に代入できるように、引数に1を返す関数も addOneという変数に代入できるのです。
なぜなら、関数だって値なのです。このおかげで、超クールなことが沢山できますよ。
それらをリストにせっとすることだって出来ます。
var f = function(x){
return x + 1;
}
var g = function(x){
return x * 2;
}
var lst = [f, g, f]; //関数のリスト!
var y = lst[1](5); //g(5) って書くのと同じだ!
関数を別の引数として渡したり、別の関数の戻り値として使うことだって出来ます。
なぜなら、関数だって値なんだもん。
ここに、callTwice 関数の定義があります。
この関数は、関数f を引数としてとり、それを二回連続で呼び出します。
var addOne = function(x) {
return x + 1;
}
var callTwice = function(f, x){
return f(f(x));
};
var y = callTwice(addOne, 3);
// y は5になります。
●やってみよう
applyTest という関数を定義しましょう。
この関数は、f という関数と x という値の二つの引数を取ります。
戻り値は、x、 xを引数にした関数 fの実行結果、そして0を引数にした関数 f の実行結果を持った配列です。
■6.
Returning Functions(戻り値で関数)
なぜなら、関数だって値なんだもん。
関数を他の関数の戻り値にすることができます。
これは、信じられないパワーを生み出すんですよ!
var makeFunction = function(){
var addOne = function(x){
return x + 1;
};
return addOne;
};
//makeFunction は、引数を取りません。
var f = makeFunction();
//f は、一つの引数を取ります。
var y = f(3);
// yは4 です。
関数と関数に引数を適用したものの、この二つの違いを良く覚えておいてください。
つまり、f と f(x) の違いです。関数が引数を一つも取らないとしても、まだ違います。
makeFunction は引数を取らない関数です。
makeFunction() というコードは、関数の実行結果を表しています。そして、それは自分とは別の関数になっています。
●やってみよう
generateという一つの引数fという関数を取る関数を定義しましょう。
これは、f 関数と入力を二倍にして返す別の関数の二つによる配列を返します。
■7.
Function Scope(関数スコープ)
多くの言語はブロックスコープを持っています。
これはつまり、変数の定義は if や for ループといったブロックの先頭で定義された変数は、そのブロック内で有効ということを意味します。
JavaScriptは、これに対してちょっと違っています。
すべての変数は関数スコープを持っています。
これは、すべての関数内で定義された変数は、関数内のどこからでも有効であることを意味します。
おそらく少し驚くんじゃないかというサンプルをお見せしましょう。
var cow = "purple";
var f = function(x) {
var r = 0;
cow = "glue";
if(x > 3){
var cow = 1; //ローカル変数としてcow
r = 7;
}
return r;
};
var z = f(2);
f(2) を呼んだ時、cowの値は"glue"に変わるでしょうか?
いいえ。cowは上記コードでは、守られます。
なぜなら、if ブロックに含まれる var cow という定義が関数全体に適用されているからです。
これは、cow が関数全体でローカル変数であるということを意味しています。
あなたの飽くなき探究心を満たすために、cow を glueに変更できるバージョンのコードものせておきましょう。
var cow = "purple";
var f = function(x){
var r = 0;
cow = "glue";
if (x > 3){
r = 7;
}
return r;
}
var z = f(2);
この驚くべき動作の為に、関数を書く際には、混乱の元にならないように関数の先頭に変数宣言を書くのをおススメします。
これは、いくつかの変数定義を、そのようなやりかたで書いたサンプルです。
var mean = function(a, b){
var sum, product, diff;
sum = a + b;
product = a * b;
diff = sum - product;
return diff;
}
●やってみよう
callFunc という関数を定義します。
この関数は、f という関数の引数を一つ取ります。
この関数は、f(0), f(0), f(1), f(1) の実行結果の配列を返しますが、f 関数を呼べるのは2回だけです。
■8.
Nested Functions(関数のネスト)
関数の中に、別の関数を定義して使うことが出来ます。ここに、複雑な問題を簡素化して解決する為にヘルパー関数を使っている例があります。
var complicated function(x) {
var f = function(y){
return y * 3 + 1;
}
return f(f(x));
};
var y = complicated(2);
// y は22になります。
関数定義内では引数に参照できますが、それと同様に、内部の関数から外側の関数の変数を参照できます。
次のサンプルはさっきと同じ動作ですが、定数の1を外に変数として出しています。
var complicated = function(x){
var c, f;
c = 1;
f = function(y){
//cをここから参照できます!
return y * 3 + c;
}
return f(f(x));
}
var y = complicated(2);
これが最初のクロージャーのサンプルです!
外側の関数が内部関数を覆い隠し、内側の関数からは外側の関数の変数を使うことができます。
より重要なこととして、内部の関数からそれを包む外部関数の引数にアクセスする場合です。
引数も変数のようなものなので、内部から同様の方法でアクセスできます。
ここに、引数 xを取り新しい関数を返す create 関数があります。
この戻された関数は、引数を受け取りませんが x の値を返します。
var create = function(x){
var f = function(){
return x;
};
return f;
};
var g = create(42);
var y = g();
// y は42です。
上記の例では、create関数の実行が完了した後にもかかわらず、戻された関数は正しく機能します。
これこそがクロージャーの魔法です。外部関数内の変数 x の参照は、外部関数の実行が終わった後でも正しく維持されています。
これは深いね!☆(ゝω・)vキャピ
改めて、クロージャーとは何でしょうか?
クロージャーとは、関数と環境を一つにまとめることです。
JavaScriptでは関数を値として渡しているとき、実はそれはクロージャーを渡しているってことなんです!
●やってみよう
genesis という名前の二つの引数 x, y を取る関数を定義してください。
この関数は、x + y の値を常に戻します。
■9.
Stateful Closures (状態を持ったクロージャー)
クロージャーの便利さの一つに、内部の状態を管理できる点があります。
内部関数から外部関数の変数にアクセスすることで、クロージャーが生成されたことを思い出してください。
これまで変数の値を読んできましたが、同様に書き込むこともできます。
ここにカウンターを作る関数のサンプルがあります。
var makeCounter = function(){
var count, f;
count = 0;
f = function(){
count = count + 1;
return count;
}
return f;
}
var counter = makeCounter();
var a = counter(); // 1が返されます。
var b = counter(); // 2が返されます。
var c = counter(); // 3が返されます。
var counter2 = makeCounter();
var d = counter2(); // 1が返されます。
var e = counter2(); // 2が返されます。
var f = counter(); // 最初のカウンターからは4が返されます。
どこに、ステータスは管理されているのでしょうか?それらは、外部スコープの変数count にあります。
では、何故コピーが2つあるのでしょうか? count 変数と、そのスコープをつかむ内部関数fを取り囲むクロージャーは、それぞれのmakeCounter関数の呼び出しで作成されたのです。
●やってみよう
makeAccumulator という名前の引数を取らない関数を作成してください。
この関数は、一つの引数を取り、関数が実行中に渡された引数の合計を戻り値として返します。
たとえば、 makeAccumulator 関数によって返される関数をfとすると、最初のf(3) の呼び出しの戻り値は3 です。
次に、f(2)を呼び出したときの戻り値は 5 になります。
■10.
Private Data(プライベートデータ)
外部関数の呼び出しが終わってしまうと、それ以降外部スコープへアクセスする手段は何もありません。
外部関数を呼び出す度に、異なる外部スコープと結び付けられた新しいクロージャーが生成されます。
このことから、閉じ込められた値は内部関数以外からは完全にプライベートで隠されていると言う事ができます。
これは、クロージャーを隠された状態を保護する為にクロージャーを使えるということを意味しています。
makeCounter のサンプルの中では、count 変数を更新できたのは内部関数だけでした。
もし複数のcounter を作ったなら、それらは個別に他からはアクセスできないプライベートなcountを持ちます。
ここに、プライベートな変数をどのように作成するかを示す例があります。
var counter = (function() {
var count, f;
f = function() {
count = count + 1;
return count;
}
return f;
}());
/* counter は引数を取らずcountを返す関数です */
var a = counter(); // 1 を返します。
var b = counter(); // 2 を返します。
var c = counter(); // 3 を返します。
これは、直前のmakeCounterの例に良く似ていますが、ここでは一つのcounterしかありません。
counter の定義の後ろの () に注意してください。さらに、関数の呼び出しも見やすくなる為に丸括弧で囲っています。(でもJSLintの為にも必要なんだよね☆(ゝω・)vキャピ)
●やってみよう
accumulator という名前の二つの引数を取る関数を作ってください。
この関数は、呼び出される度に渡された全ての引数の合計を返します。
11.
Asynchronous Callbacks(非同期コールバック)
関数を呼び出すとき、処理はブロックされており先に進むことは出来ません。次のステップは待たされます。
たとえば、read(file) という関数を呼んだ場合、この関数は読み出しの準備までに、正しい位置までディスクが回転するのを待つでしょう。
代わりの解決策として、read 関数がパラメーターにfile とcallback の二つのパラメーターを取る方法があります。
関数は、ファイルを読むスケジュールを作成して、ファイルを読む前に呼び出しから即座に戻ります。
データが読まれたなら、callback関数がデータとともに呼び出されます。
これは、ファイルからカウンターを読み込む処理をブロックするモデルのサンプルです。
var readFile = function(){
var data;
data = read('file.txt');
return 'Contents: ' + data;
};
こっちは、同じサンプルですが非同期のread関数を使っています。
var readFileAsync = function(callback){
var func = function(x){
callback('Contents: ' + x);
};
read('file.txt', func);
};
readFileAsync は、呼んだデータの処理が書かれた callback関数 を渡して呼びます。
readFileAsync の中では、callback 関数と共に read 関数を呼んでいます。
データが読まれたとき、func はデータと共に呼ばれます。そして、func は、データに含まれる文字列をアップデートしたものを渡して callback 関数を呼び出しています。
非同期処理を使ったプログラムを書いていると、何か順番が逆になっている感じがするんじゃないでしょうか。
こんなとき、無名関数が便利です。ここに、無名関数を使った例を示します。
var readFileAsync = function (callback) {
read('file.txt', function(x) {
callback('Contents: ' + x);
});
};
次のサンプルは、非同期関数の write(file, contents, callback) を使っています。
ここでは callback 関数は引数をとらず、ファイルの書き込みが完了したときに呼び出されるのみです。
var writeScoreAsync = function(score, callback){
var contents = 'Your score was ' + score;
write('hiscore.txt', contents, function() {
callback();
});
};
●やってみよう
doStuffAsync という名前の関数を定義します。この関数は、callbackという引数を一つ取ります。
この関数は、passwords ファイルの中身を読み、world.txt というファイルに”OWNED"という文字列を加えて書き出します。
そして、callback 関数を呼び出します。
read、write関数とも非同期のものを使ってください。
■12.
Continuation Passing(継続を渡す)
Continuation(継続)は、次に実行する何かを表現したコールバック関数の一種です。
関数から戻る方法以外で、処理を継続させる為にContinuation を呼び出します。
もし、あなたのプログラム全体を return を使わずに、continuation の呼び出しのみに置き換えたなら、それは continuation-passing スタイルのプログラムに置き換えたことになります。
次のサンプルは、数値に1を加える関数ですが、通常タイプと、continuation-passing スタイルで書いています。
var addOne = function(x){
return x + 1;
};
// CPS (continuation-passing-style)
var addOneCPS = function(x, ret) {
ret(x + 1);
};
continuation-passing スタイルでは、全ての関数が値を返す替わりに呼び出される、追加のret 引数を取ります。
もう値なんて返さない!超いけてるよね!あげぽよ~(´q`)
continuation を使うのは、一つという制限はありません。全ての関数は、success と failure のcontinuation を受け取るかもしれない。
ここに、addOne をそんな感じにしたサンプルがあります。
var addOneC = function( x, success, failure){
if(x < 10){
success(x + 1);
}else{
// x でかすぎ
failure();
}
}
次のサンプルは、処理を順番に実行する seqC です。関数 seqC は二つの関数 fC, gC に加えて、success, failure を引数に取ります。
関数 fC, gC も、continuation の success とfailure を引数に取ります。それらはデータは処理しません。
seqC は、fCを実行し、その結果がsuccess の場合に処理を継続します。
もしfCが失敗の場合、gCを実行してから処理を継続します。
var seqC = function (fC, gC, success, failure) {
var f_success, f_failure;
f_success = function(){
success();
}
f_failure = function(){
var g_success, g_failure;
g_success = function(){
success();
};
g_failure = function(){
failure();
};
};
gC(f_success, f_failure);
);
このコードは、実際にはもっと単純に出来ます。たとえば、f_successの替わりに successを使うことも出来ます。
次はシンプルにしたバージョンです。
var seqC = function(fC, gC, success, failure){
fC(success, function(){
gC(success, failure);
});
};
●やってみよう
seqCに似た bothC という名前の関数を定義しましょう。
この関数は引数に、関数 fC, gC と、continuation の success, failureを取ります。
関数 fC, gCは両方とも、success, failure を引数に取ります。
bothC は、関数fC、gc をエラーか成功かにかかわらず順番に実行します。
ただし、success を呼び出すのは両方の関数が成功した場合のみです。それ以外はfailureを実行します。
関数は決して値を返さないことを忘れないでください!
---
オシマイ!(゚∀゚)
テーマ:
プログラミング
- ジャンル:
コンピュータ