2010年10月20日水曜日

メモ: クロージャの書き方

クロージャとは?

JavaScriptに触れていると、クロージャという書き方に突き当たります。それ何?って思って検索しても、難しいことばかりで本質はなかなか分かりません。しかも、他の言語でどう書くかも掲載されないことが多いです。元々慣れない言語(JavaScript)を勉強しているときに、概念もあやふやで、他の言語の例もない・・・ってことで使えるようになるまで苦労しました。確かに、他の言語では書き表しにくい書き方です。

そうは言っても、避けてばかりでは進歩しないので、自己流で解説してみます。サンプルは、node.jsなどを想定して書いています。Debianのsqueeze(2010/10/20時点でtesting)では、すでにnode.jsが標準パッケージに入っているので、敷居はかなり低くなっています。

まずはJavaScriptでクロージャ

ゴタゴタ書くと、難しいという先入観が形成されてしまうので、まずは下記のソースをお読み下さい。
var test = function() {
  var i = 0;
  return function() {
    i += 1;
    return i;
  }
}

var count = test();
count();  // => 1
count();  // => 2
count();  // => 3
まず、変数countにはtest内の3行目以降で宣言したfunctionが代入されます。堅い言語に慣れている人は、まずこの書き方を許容して下さい。functionは変数に代入できます。
このfunction内で扱っている変数iは、functionの外で宣言されています。ここで、count()が実行されると、iが1増えます(iが1になります)。そして、このiの値は(プログラマから見て)裏側で保持され、再度count()が実行されると、今度はiが2になります。このように、スコープ外で宣言された変数を取り扱うのがクロージャです。

では、iはどこに保持されているかと言うと、概念的にはcountの一部として保持されています。例えば、別の変数も作ってみましょう。8行目までは上記と一緒で、9行目から先を以下のように改造したとします。
var count1 = test();
var count2 = test();
var count3 = test();
count1();  // => 1
count2();  // => 1
count1();  // => 2
count3();  // => 1

このように、count1, count2, count3でのiはそれぞれ別の値を保持しています。もちろん、test内でいくつもの変数を宣言することも可能です。

Rubyでクロージャ

次に、比較のためにRubyでクロージャを扱ってみましょう。例によって、まずは下記のコードをご覧下さい。
def test
  i = 0
  return lambda { i += 1 }
end

count = test
count.call  # => 1
count.call  # => 2
count.call  # => 3
count.callの文字数が多くて嫌いな人は、最新版のRubyならcount[]という書き方も許されます。

Rubyを知らない人のために、じっくりと説明していきます。まず、1行目のdefはメソッドを定義するための構文です。この例ではtestという名前のメソッドを定義しています。2行目で変数iに0を代入しています。変数宣言はRubyでは不要です(と言うか、できません)。3行目でlambdaを使って定義した一連の作業を返しています。lambdaは、「一連の作業をオブジェクト化する」ことが可能です。(Rubyでは、最後に評価された内容がそのまま返り値となるので、returnは省略可能です)

6行目で変数countにtestメソッドの実行結果として先ほど定義したlambdaが代入されます。「test」は「test()」の省略形です。7行目以降のcount.callは、lambdaの実行です。iがどこに保持されているかはともかく、iの値が1ずつ増えているのが分かると思います。もちろん、count1, count2などを定義すると、それぞれのiは独立した変数として保持されます。

ついでにカリー化の手前

上のサンプルでは、クロージャの良いところが伝わりきっていないと思いますが、そのままカリー化の手前まで突っ走りたいと思います。カリー化の手前とは「2つの引数を伴う関数f(x,y)をg(x)(y)のように記述すること」です。真のカリー化は、もう少し複雑なので説明は割愛します。こちらも早速サンプルをご覧下さい。
var test2 = function( x ) {
  return function( y ) { return x * y }
}

mul = test2(3);
mul(2);  // => 6
mul(5);  // => 15

def test2( x )
  lambda { |y| x * y }
end

mul = test 3
mul.call 2  # => 6
mul.call 5  # => 15
2つの引数x, yを受け取って積を返す関数の代わりに、一つ目の引数xを先に受け取りクロージャの要領で保持しておきます。その後、もう一つの引数yを受け取って積を計算し、返すという流れが記述できました。もう一つの引数の受け取り方ですが、Rubyでは2行目の「 |y| 」のように、パイプ記号で囲った変数に代入されます。

この例では、掛け算に使用する数値の一つを先に受け取り、以後はその数値を基準として、もう一つの数値は実際に計算する際に渡す形となっています。これ以外にも、初期値を指定してカウントダウンするような場合とか、出力フォーマットを定めておいて後で数値を渡すと指定したフォーマットで出力するなどの場合に便利ではないでしょうか?

クロージャまとめ

C言語では、スタティック変数を使用することで、一見似た様な記述が行えます。しかし、その場合はcount1とcount2のように二つ以上の状態を保持できません。だからと言ってグローバル変数を使用するのも気が引けます。そのような場合にクロージャのような書き方ができると、グローバル変数を使用することなく実装できます。

一方、「クロージャなんて使わずに、オブジェクト指向(クラスとかインスタンス)でいいんじゃない?」って考えもあると思います。もちろん、オブジェクト指向の記述方法でも同じことができます。しかしその場合、変数の値を裏で保持して欲しいだけなのに、大げさな記述が必要になってしまいます。Rubyでのサンプルを以下に示します。
class Test
  def initialize
    @i = 0
  end
  def count
    @i += 1
  end
end

c1 = Test.new
c1.count  # => 1
c1.count  # => 2
c1.count  # => 3
この程度のプログラムでしたら、クロージャでもクラスでも好きな方で記述して良いと思いますが、クロージャのシンプルさは魅力的です。そうは言ってもクラスを使ってもカリー化と同等の内容も簡単に記述できます。クロージャに慣れていない人は現時点では多いと思いますので、そう言う人に気を遣う場合にはクラスを使った方が良いと思います。 このエントリーをはてなブックマークに追加