RubyでもJavaScriptみたいなクロージャを作ってみる

2012.11.16

こんにちは。やまがたです。
最近のJavaScript人気に便乗して、その特徴であるクロージャをRubyでも使ってみたいとおもいます。

まずはよくあるなJavaScriptによるクロージャの例です。

var f = (function f() {
  var i = 0;
  return function() {
    console.log(i++);
  }
})();

f() // => 0
f() // => 1
f() // => 2
f() // => 3

これをRubyで書いてみるとこうなります。

def f
  i = 0
  Proc.new { puts i; i += 1 }
end
_f = f

_f.call # => 0
_f.call # => 1
_f.call # => 2
_f.call # => 3

それぞれの違いを見ていきましょう。
まずJavaScriptが変数 f に生成したクロージャを代入しているのに対し、Rubyでは一旦クロージャ生成メソッドを定義してから変数 _f にクロージャを代入しています。
もちろんJavaScriptでもRubyのような書き方はできますが上記がそれぞれの言語の一般的な書き方かと思います。

また上記の例では、JavaScriptでは関数の実行方法は1つ(末尾に"()"をつける)しかありませんが、Rubyでは何もせず単に f と書くだけで実行している場合と、.call を呼び出して実行している場合の2種類が存在します。
これはJavaScriptでは括弧を省略すると関数オブジェクトとして扱えますが、Rubyではメソッドの呼び出しと変数の記述に違いがないために f と書いただけでクロージャ生成メソッドが実行され、生成されたクロージャがここでは Procオブジェクト であるためにその実行に .call というメソッドの呼び出しが必須となります。

ちなみにRubyでは別パターンとして以下のような記述もできます。

def f
  i = 0
  lambda { puts i; i += 1 }
end
_f = f

_f[] # => 0
_f[] # => 1
_f[] # => 2
_f[] # => 3

こちらではクロージャの生成に Karnel#lambda を使っています。どちらもProcオブジェクトが生成されます。またProcオブジェクトの実行に [] を使っています。配列のようにも見えてしまうので若干違和感がありますね...。

余談ですが別の方法として以下のような書き方も文法的には間違いではありません。

def f
  i = 0
  def _f
    puts i; i += 1 
  end
  method(:_f)
end
_f = f

_f.call # => undefined local variable or method `i' for main:Object (NameError)

ただし.callでメソッドを呼び出しても変数iを見つけられずにエラーとなってしまいます。これはJavaScriptの感覚からすると少し変な感じがしますね。Rubyのdefによるメソッドの定義では外のローカル変数は参照できないのです。
しかしProc.newやKarnel#lambdaを使った場合はその限りではありませんのでRubyでクロージャを作るときは迷わずこちらを使いましょう;-)

まとめ

JavaScriptもRubyもオブジェクト指向の言語ということで共通する部分も多いですが、JavaScriptでは関数がファーストクラスのオブジェクトとして扱われるのに対し、Rubyはそうではないという性質が今回のようなコードを試してみるとより理解できるようになるのではないでしょうか。