クロージャ

関数、オブジェクト、クロージャから抜粋)

OOPオブジェクト指向プログラミング(Object Oriented Programming)
FP関数プログラミング(Functional Programming)

関数、オブジェクト、クロージャの使い分けについて考えます。
関数型が良いのか、オブジェクト指向が良いのか、知りたいと思っていました。
カウンタを例にして、関数、スコープ、オブジェクト、クロージャの順に見て行きます。

関数

関数は処理です。
入力と出力があります。
関数型プログラミングでは、関数同士の入力と出力を連結しプログラムが構成されます。

#highlighter(javascript){{
var current = 0;

function next(v){ return v + 1 }

function previous(v){ return v - 1 }

ok( 1 == ( current = next(current) ) );
ok( 2 == ( current = next(current) ) );
ok( 1 == ( current = previous(current) ) );
}}

関数は純粋に処理のみを提供し、変数は外部に切り離されて保存されます。
Counter.nextのようにクラスメソッドとして、別の名前空間に置かれることもありますが、本質的には単なる関数です。

  • 短所
    関数は、データと処理を外部のロジックが引き合わせる必要があります。
    データと処理が密接な関係を持つ場合は、お互いが予めくっついているオブジェクトの方が良いです。
  • 長所
    入力と出力のみに気を配り処理を作成すればよいため、テストがしやすく、品質が高くなります。
    安く書けます。
    その関数が知らないうちに、依存しているデータが書き換わっていることはありません。

関数の外部とはインターフェイスにより、きっぱりと隔てられているため、移植性と再利用性が高くなります。

スコープ

スコープはデータが保存されている場所です。

JavaScriptでは、関数ごとにスコープが分かれています。
ifブロックや、forブロックなど、ブロックごとのスコープはありません。

#highlighter(javascript){{
var scope = "GLOBAL";

(function local_1(){
var scope = "LOCAL_1";

(function local_2(){
var scope = "LOCAL_2";

ok(scope == "LOCAL_2");
})()

ok(scope == "LOCAL_1");
})()

ok(scope == "GLOBAL");
}}

関数の中に関数を定義することで、入れ子のスコープを作れます。
入れ子のスコープ同士は結びつけられ、スコープチェーンを構成します。
スコープの中で変数に対応する値が見つからない場合は、チェーンの中の一つ上のスコープで値が探されます。

オブジェクト

オブジェクトは、データに処理がくっついたものです。
オブジェクトに付いた関数は、メソッドと呼ばれます。

#highlighter(javascript){{
function Counter(){
this.current = 0;
}

Counter.prototype.next = function(){
return ++this.current;
}

Counter.prototype.previous = function(){
return --this.current;
}

var counter = new Counter();

ok( 1 == counter.next() );
ok( 2 == counter.next() );
ok( 1 == counter.previous() );
}}

Counterは、関数の時よりも、断然使いやすそうですね。

  • 短所
    一つのスコープへ、複数の処理がアクセスするため、動きが複雑になり、作るのにお金がかかります。
    関数と比べると、テストも難しいです。
  • 長所
    処理とデータが一まとまりになり、外部のプログラムへ使いやすいインターフェースを提供できます。
    また、状態を持つ物体としてイメージできるため、抽象的に考えやすくなります。

型が無い言語では、処理の引数の型を制限する働きもあります。
StringクラスのtoLowerCase?メソッドは、スコープを使わず、Stringオブジェクトの状態を変化させないため、単なる関数でも充分です。
しかし、Stringオブジェクトにくっつき、そこからのみ呼び出せる状態にすることで、異物が引数に入るのを防いでいます。

オブジェクトには、名前空間を区切る働きもあります。
名前空間を分けることで、同じ名前の関数を複数の場所で作ることができます。
また、たくさんの処理を分類し整理できるため、管理が楽になります。

クロージャ

クロージャは、処理にデータがくっついたものです。
クロージャは、オブジェクトの裏返しと言えます。

下のサンプルでは、カウントする関数に、生成元のcreateCounterのスコープがくっついています。

#highlighter(javascript){{
function createCounter(){
var current = 0;

return function(){
return ++current;
}
}

var counter = createCounter();

ok( 1 == counter() );
ok( 2 == counter() );
ok( 3 == counter() );
}}

オブジェクトは、通常new演算子により、コンストラクタが呼び出され生成されます。
対してクロージャは、関数を生成するファクトリ関数により生成されます。

クロージャは、「処理が一つしかないオブジェクト」と考えることもできます。
ちょこっとだけ簡便的に利用するためのワンショットオブジェクトとも考えられます。

  • 短所
    クロージャは、リッチな機能は提供できません。
    カウンタのサンプルでは、オブジェクトの時にあった、前へ戻る機能が無くなっています。
  • 長所
    オブジェクトに比べ、コードが短くなります。
    ただ呼び出すだけの、シンプルなインターフェースを外部へ提供できます。
    つまり、簡単なことが、簡単にできます。

まとめ

スコープと、関数、オブジェクト、クロージャの関係を、「大まかに」まとめると以下のようになります。

scope.gif

スコープあたりの処理の数で、クロージャ、オブジェクト、関数を分類できます。

分類スコープの数処理の数
クロージャ1つのスコープ1個の処理
オブジェクト1つのスコープ複数の処理
関数スコープなし1個の処理

ここまでを基にすると、こんな具合に関数、オブジェクト、クロージャの使い分けを考えられます。

カウンタは、スコープがあり状態を保持する必要があるため、関数は向いてないな。
簡単なカウンタだったらクロージャで充分で、リッチなカウンタだったらオブジェクトが便利だな、と考えられます。

コントロールに色を付け、徐々に色が薄くなっていくような処理だったらどうでしょうか?
必要な処理は、色を薄くする処理の一つだけなのでオブジェクトは使う必要はなさそうです。
クライアントにストップなどの処理を一つ渡す必要があるならクロージャ、そうではなくスタートだけなら関数で充分だと思います。

クロージャで良いところへオブジェクトを持ってくるのは大げさです。
複雑な状態を持つものを関数やクロージャで書くのも無理があります。
それぞれのメリットを生かし、適材適所でミックスして使いプログラミングをするのが良いと思っています。

参考

クロージャ - Google検索
クロージャ - Wikipedia
クロージャとは - はてなダイアリーキーワード

【closure】閉包。レキシカルクロージャとも言う。

静的スコープ(レキシカルスコープ)を実現するために必要となる。

関数内に出現する自由変数(関数内で宣言されていない変数)の解決の際、実行時の環境ではなく、関数を定義した環境の変数を参照できるようなデータ構造。

「推移的閉包【transitive closure】」とかのクロージャとは全く関係がないのに同じ名前なことが、言語を越えて学習者を無意味に悩ませている

プログラミング言語の構文 - Wikipedia


添付ファイル: filescope.gif 7件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-01-20 (日) 23:30:03 (2071d)