http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html :
closures are in fact poor man's objects (in his opinion).
...snip...
A closure is an object that supports exactly one method: "apply".
Christian Queinnecは、
- closures are a poor man's objects
と言っている、一方、Norman Adamsは、
- objects are a poor man's closures
と言っている、と、そんなことみたいです。「クロージャはよく知らないがオブジェクトなら知ってる」という人は多そうなので、「クロージャとは、貧乏人のオブジェクトだ」って説明は分かりやすいかも、と思ったりしました。
内容:
カウンター・クラスと貧乏なカウンター・クラス
まずは、ごく普通のカウンター・クラス。TypeScriptで書いてあります。
// カウンター・クラス class Counter { private init:number; private val:number; constructor(init:number = 0) { this.init = init; this.val = this.init; } up(count:number = 1) : void { this.val += count; } down(count:number = 1) : void { this.val -= count; } value() : number { return this.val; } reset() : void { this.val = this.init; } } // カウンター・オブジェクトを使ってみる var c = new Counter(10); c.up(); c.up(); console.log(c.value()); // 12 と表示される
クラスのメソッドは1つに限るという制約があったらどうしましょうか。up()とvalue()をひとつにまとめたupval()というメソッドでしのぐことにします。
// 貧乏なカウンター・クラス class PoorCounter { private init:number; private val:number; constructor(init:number = 0) { this.init = init; this.val = this.init; } // メソッドは1つだけ upval(count:number = 1) : number { this.val += count; return this.val; } } // 貧乏なカウンター・オブジェクトを使ってみる var pc = new PoorCounter(10); pc.upval();pc.upval(); // 値は捨てる console.log(pc.upval(0)); // 12 と表示される
reset()メソッドはなくなってしまいましたが、まー、なんとかカウンターとして使えます。
貧乏なカウンターのクロージャによる実現
貧乏なカウンター・クラス/オブジェクトと同じ機能をクロージャを使って実現してみます。TypeScriptなので、引数/戻り値/変数にすべて型が付いてますが、とりあえずは気にしないでください(無視してよい)。型注釈(コロンとそれに続く型)と引数デフォルト値を取り除けばJavaScriptコードになります。
// カウンター・クロージャを生成する関数 function createCounter(initArg:number = 0) : (count?:number)=>number { var init:number; var val:number; init = initArg; val = init; function upval(count:number = 1) : number { val += count; return val; } return upval; } // カウンター・クロージャを使ってみる var cc = createCounter(10); cc();cc(); // 値は捨てる console.log(cc(0)); // 12 と表示される
貧乏なカウンター・クラス/オブジェクトとの類似点と違いに注目してください。createCounter()を生成関数、生成関数の内側で定義されたupval()をメソッド関数と呼ぶことにします、ここだけの話だけど。
- クラスのインスタンス変数は、クロージャの生成関数のローカル変数になっている。
- クラスのコンストラクタの動作(初期化)は、クロージャの生成関数の動作になっている。
- クラスのメソッドは、クロージャのメソッド関数(入れ子の関数)になっている。
- newの代わりに、クロージャの生成関数を呼ぶ。
- それにより、オブジェクト=メソッド付きのデータの代わりに、クロージャ=データ付きの関数が生成される。
クラス/オブジェクトの場合は、newによって生成されたインスタンス・データが、クラスに所属するメソッドへの参照を持っている、という感じです*1。
それに対してクロージャの場合は、メソッド関数が内部で使う非ローカル変数を自分専用に持っています。自分専用の非ローカル変数の正体は、生成関数のローカル変数です。通常、関数のローカル変数は関数の実行が終わると消え去ります。しかし、生成関数のローカル変数valはクロージャ=メソッド関数から参照され続けているので、消すに消せずに残っているのです。
オブジェクトにおいてはインスタンスデータが主役で、そこからメソッドが参照されている。一方、クロージャでは単一のメソッド関数が主役で、そこからデータ(生き残っている生成関数のローカル変数達)が参照されているわけです。データと関数、どちらに主眼を置くかの違いだけで、本質的には同じです。
貧乏人だって頑張れば…
クロージャでは単一のメソッドしか実現できませんでした。それゆえに貧乏なオブジェクトと言われたのです。しかし、単一のメソッドをディスパッチャー(仕事の振り分け)に使えば、複数のメソッドをサポートすることもできます。
// リッチなカウンター・クロージャを生成する関数 function createRichCounter(initArg:number = 0) : (name:string, ...args:any[])=>any { var init:number; var val:number; init = initArg; val = init; function up(count:number = 1) : void { val += count; } function down(count:number = 1) : void { val -= count; } function value() : number { return val; } function reset() : void { val = init; } function dispatch(name:string, ...args:any[]) : any { switch (name) { case 'up': up.apply(null, args); return; case 'down': down.apply(null, args); return; case 'value': return value.apply(null, args); case 'reset': reset.apply(null, args); return; default: throw "Method missing: " + name; } } return dispatch; } // リッチなカウンター・クロージャを使ってみる var rcc = createRichCounter(10); rcc('up');rcc('up'); console.log(rcc('value')); // 12 と表示される
rcc.up(2) の代わりに rcc('up', 2)、rcc.value() の代わりに rcc('value') のように書かなくてはならないので不格好です。しかしこの問題は、構文的な細工が可能なら修正できます。機能的には元のカウンター・クラス/オブジェクトとなんら遜色ありません。
クロージャさえあれば、その応用としてオブジェクトは実装できるんだから、オブジェクトよりクロージャのほうがエライんだぞ、というのが"objects are a poor man's closures"の根拠です。どっちがエライかはどうでもいいですが、相互に相手を定義できる関係にある、ということがポイントですね。
*1:絵は、クラス/オブジェクトのメモリレイアウトを正確に反映しているわけではなくて、あくまでも「感じ」です。