Hatena::ブログ(Diary)

このブログは証明できない。

2009-01-01

[]Rubyのblock、Proc、lambdaを理解する

新年おめでとうございます。2009年の最初のネタはプログラミングのネタにすることにしました。


Rubyについてのステキなエントリーがあったので、紹介します。

私は翻訳能力がないばかりか、リーディング能力も貧相です。ぜひ、原文を読んでみてください。


はじめに

Rubyのblock、Proc、lambdaはパワフルですが、解りにくい。Rubyクロージャを使う方法が4つあって、それぞれチョットずつ違います。ここでは、そのへんを解説したいと思います。


Block

もっとも簡単で、かつRubyっぽいと言えば、Blockですね。

array = [1, 2, 3, 4]
 
array.collect! do |n|
  n ** 2
end
 
puts array.inspect
 
# => [1, 4, 9, 16]

何が起こっているか?

  1. まず、block付きでArrayの"collect!"メソッドを呼びます。
  2. blockが実行されます。変数nが2乗されます。
  3. 配列の各要素が2乗されました。

"collect!"は、配列の各要素でblock中のコードを使っていることに注意してください。次に、独自の"collect!"メソッドを実装してみます。"iterate!"メソッドを作ってみましょう。

class Array
  def iterate!
    self.each_with_index do |n, i|
      self[i] = yield(n)
    end
  end
end
 
array = [1, 2, 3, 4]
 
array.iterate! do |n|
  n ** 2
end
 
puts array.inspect
 
# => [1, 4, 9, 16]

Arrayクラスを再オープンして、"iterate!"メソッドを追加しました。破壊的メソッド(オブジェクトの状態が変わるメソッド)なので、Rubyの習慣に習って"!"を付けています。"iterate!"メソッドは"collect!"メソッドと同じ動きをします。


メソッドの中でblockの名前を特定する必要はありません。かわりにyieldキーワードを使うことができます。yieldキーワードはメソッドに与えられたblockを実行します。そして、"n"をyieldに渡すことができます。


  1. Arrayの"iterate"を呼びます。
  2. "n" (1, 2, ...) 付きでyieldが呼ばれると、blockに数値が渡されます。
  3. blockは数値を受け取って2乗します。最後の値を処理すると、自動的にreturnします。
  4. yieldはblockの返り値を出力し、配列の値が書き変わります。
  5. 配列の各要素の分だけ繰り返されます。

値を2乗したり、文字列に変換したり、スクリーンに出力したり、柔軟な処理ができます。


yieldではなく、Procを使ってみましょう。

class Array
  def iterate!(&code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
 
array = [1, 2, 3, 4]
 
array.iterate! do |n|
  n ** 2
end
 
puts array.inspect
 
# => [1, 4, 9, 16]

1つ前の例に似ていますが、2つの違いがあります。1つ目は、"&"のついた引数を与えていることです。2つ目は、yieldの代わりに"call"を使っていることです。結果はまったく同じです。しかし、この違いからblockについて学ぶことができます。

def what_am_i(&block)
  block.class
end
 
puts what_am_i {}
 
# => Proc

blockは単にProcだったのです!それでは、Procとは何でしょうか?


Proc

同じblockを何度も使いたいときがあります。そんなときに再利用するためのコードがProcです。blockとProcの唯一の違いは、blockは保存できないということです。Procについて見てみましょう。

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
 
array_1 = [1, 2, 3, 4]
array_2 = [2, 3, 4, 5]
 
square = Proc.new do |n|
  n ** 2
end
 
array_1.iterate!(square)
array_2.iterate!(square)
 
puts array_1.inspect
puts array_2.inspect
 
# => [1, 4, 9, 16]
# => [4, 9, 16, 25]

blockが小文字で、Procが大文字の理由

blockはクラスではなく、Procはクラスです。そこで、blockは小文字で、Procは大文字で書いています。同じ理由で、lambdaも小文字です。


"iterate!"メソッドの引数に"&"を付けない方法に注目しましょう。Procをを渡すことは、他の型の引数を渡すのと同じです。Procは他のオブジェクトと同じように扱われます。

class Array
  def iterate(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
 
array = [1, 2, 3, 4]
 
array.iterate(Proc.new do |n| 
  n ** 2
end)
 
puts array.inspect
 
# => [1, 4, 9, 16]

上記の例は、他の言語がクロージャを扱うのとまったく同じです。でも、Rubyっぽくありませんね。


blockを単独で使わない場合はどうでしょう?メソッドに複数のクロージャを渡したいときは?Procを使うと、こう書けます。

def callbacks(procs)
  procs[:starting].call
 
  puts "Still going"
 
  procs[:finishing].call
end
 
callbacks(:starting => Proc.new { puts "Starting" },
          :finishing => Proc.new { puts "Finishing" })
 
# => Starting
# => Still going
# => Finishing

Procではなくblockを使うのはどんな場合でしょうか?

  • block: メソッドが小分けにできる場合。ユーザーに小分けにした断片を使わせたいとき
  • block: データベースマイグレーションのように、複数の表現を自動的に実行したいとき。
  • Proc: 1つのblockを複数回使いたいとき。
  • Proc: メソッドが1つまたは複数のコールバックをとるとき。

Lambda

これまでに、Procを2通りの方法で使ってきました。直接渡す方法と、変数に保存する方法です。これは、他の言語の無名関数やlambdaによく似ています。lambdaはrubyでも使うことができます。

class Array
  def iterate(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
 
array = [1, 2, 3, 4]
 
array.iterate(lambda { |n| n ** 2 })
 
puts array.inspect
 
# => [1, 4, 9, 16]

一見すると、lambdaはProcと同じに見えます。しかし、2つの微妙な違いがあります。1つ目は、Procと違って、lambdaは引数の数をチェックするということです。

# example-9.rb
 
def arguments(code)
  one, two = 1, 2
  code.call(one, two)
end
 
arguments(Proc.new { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })
 
arguments(lambda { |a, b, c| puts "Give me a #{a} and a #{b} and a #{c.class}" })
 
# => Give me a 1 and a 2 and a NilClass
# *.rb:10: ArgumentError: wrong number of arguments (2 for 3) (ArgumentError)

Procだと、余分な変数には"nil"が入りますが、lambdaではエラーが発生します。


2つ目の違いは、lambdaはdiminutive returnsがあるいということです。Procのreturnはメソッドを中断します。lambdaは値を返してメソッドを続行します。解りにくいですか?例を見てみましょう。

def proc_return
  Proc.new { return "Proc.new"}.call
  return "proc_return method finished"
end
 
def lambda_return
  lambda { return "lambda" }.call
  return "lambda_return method finished"
end
 
puts proc_return
puts lambda_return
 
# => Proc.new
# => lambda_return method finished

"proc_return"では、returnキーワードがあると、処理を中断します。一方、"lambda_return"メソッドは"lambda"という文字列を返すlambdaがあっても、次の"lambda_return method finished"を出力します。なぜ、違うのでしょうか?

この違いは、処理とメソッドのコンセプトの違いにあります。Procはメソッドではなく、コードスニペットです。このため、Procのreturnは"proc_return"メソッドのreturnになります。lambdaはメソッドと同じように動作します。引数の数をチェックし、返り値を返します。lambdaは、匿名のメソッドを書くのと同じことなのです。


下のような場合は、Procの代わりに匿名メソッド(lambda)を使う必要があります。

def generic_return(code)
  code.call
  return "generic_return method finished"
end
 
puts generic_return(Proc.new { return "Proc.new" })
puts generic_return(lambda { return "lambda" })
 
# => *.rb:8: unexpected return (LocalJumpError)
# => generic_return method finished

lambdaはメソッドと同じように振る舞いますから、returnを使うことができます。この意味の違いは、下の例に現れます。

def generic_return(code)
  one, two    = 1, 2
  three, four = code.call(one, two)
  return "Give me a #{three} and a #{four}"
end
 
puts generic_return(lambda { |x, y| return x + 2, y + 2 })
 
puts generic_return(Proc.new { |x, y| return x + 2, y + 2 })
 
puts generic_return(Proc.new { |x, y| x + 2; y + 2 })
 
puts generic_return(Proc.new { |x, y| [x + 2, y + 2] })
 
# => Give me a 3 and a 4
# => *.rb:11: unexpected return (LocalJumpError)
# => Give me a 4 and a 
# => Give me a 3 and a 4

"generic_return"は2つの値を返すクロージャを期待しています。lambdaだと簡単ですね。


lambdaの代わりにProcを使うべきなのはいつでしょうか?これはクロージャをどう見るかですね。blockを渡したいというだけならProcを使います。あるメソッドを別のメソッドに渡し、かつreturnに意味があるならlambdaを使います。lambdaがオブジェクトの形をしたメソッドならば、それを既存のメソッドに保存したり受け渡したりできるのでしょうか?Rubyの場合、ちょっとトリッキーです。


Method Object

既存のメソッドをクロージャとして他のメソッドに渡すことができます。Rubyの"method"メソッドが使えます。

class Array
  def iterate!(code)
    self.each_with_index do |n, i|
      self[i] = code.call(n)
    end
  end
end
 
def square(n)
  n ** 2
end
 
array = [1, 2, 3, 4]
 
array.iterate!(method(:square))
 
puts array.inspect
 
# => [1, 4, 9, 16]

この例では、すでにある"square"メオソッドを"iterate!"メソッドに渡しています。このときのオブジェクトの型は何でしょうか?

def square(n)
  n ** 2
end
 
puts method(:square).class
 
# => Method

思った通り、"square"はProcではなくMethodです。コンセプトが同じであれば、このMethodオブジェクトはlambdaと同じように振る舞うはずです。しかし、"square"は名前付きのメソッドで、lambdaは匿名メソッドです。lambdaはMethod型なのでしょうか?

puts lambda {}.class
 
# => Proc

lambdaはProcでした。なぜ?正直、lambdaがなぜMethodオブジェクトでないのか解りません。おそらく、Rubyの後のバージョンでは変更されるのではないでしょうか。


まとめ

Rubyの4つのクロージャである、block、Proc、lamblda、Methodを見てきました。blockとProckはコードスニペットとして振る舞い、lambdaとMethodはメソッドのように振る舞うことが解りました。コードの例から、どんな場合にどれを使えば効果的か解ったと思います。それでは、表現豊かで面白いRubyの機能を試してみてください。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証