LoginSignup
48
39

More than 5 years have passed since last update.

Rubyのブロック、Proc、Lambdaって何?

Last updated at Posted at 2018-04-25

Rubyをはじめると、ブロックとかProcとかLambdaとか、
みんな似たような雰囲気をしているから、うまく理解しづらいですよね。

復習も兼ねて、余計な説明抜きで、ブロック・Proc・Lambdaについて説明していきます。

ブロック

ブロックはクロージャです。
ですので、ブロックを使うことで、普段参照できないローカル変数を参照できるようになります。

block.rb
class Sample
  x = 1
  y = 1

  define_method :output do
    x + y
  end
end

sample = Sample.new
puts sample.output # => 2

上記のコードにある、define_methodの、
doからendまでの部分({}を利用することもできます)がブロックです。

each文やmapもブロックのロジックを実行します。

block.rb
x = 10
array = [1, 2, 3, 4]

# each
array.each do |num|
  puts x + num # => 11, 12, 13, 14
end

# map
array.map do |num|
  puts num * x # => 10, 20, 30, 40
end

また、ブロック内のロジックが一文だけなら、

block.rb
# each
array.each { |num| puts x + num }

# map
array.map { |num| puts num * x }

このように記述すると読みやすいかもしれません。

ブロックはインスタンスメソッドに渡して、メソッド内で利用することもできます。

block.rb
x = 1

def output
  yield * 10
end

puts output { x * 10 } # => 100

outputメソッドはスコープの違う変数xを参照することができませんが、
クロージャであるブロックは別です。
outputの引数としてxを含めたブロックを渡し、メソッド内のyieldでそれを展開しています。

ブロックは、メソッド定義時に明示しなくても、呼び出し時に引数としてブロックを記述することで、ブロックが渡されたと判断されます。
その後にyieldがブロックを展開するのです。
こうして、メソッドはそのスコープの外にあるxを利用して、計算を実行することができるようになりました。

上のコードの場合、outputメソッドの中身は、
(x * 10) * 10
となるわけです。

Proc

しかし、ブロックは利用の仕方に限りがあります。

ブロックは、いわば、バラバラのコードを束ねた塊に過ぎず、これ自体はオブジェクトとして扱われません。
ですので、ブロックは変数に渡すことができず、利用するときはブロックを利用するメソッド(newやdefine_methodなど)の利用時に限られてしまいます。

このようなブロックの性質を補うのが、Procです。
Procは、ブロックをオブジェクトに変換します。

proc_statement.rb
x = 1
y = 1

block = Proc.new { |z| (x + y) * z }
puts block.call(10) # => 20

Proc.newにブロックを渡すと、そのブロックのオブジェクトを作成します。
上記のコードでは、変数blockにブロックのオブジェクトを格納しています。

出力するときは、callメソッドで呼び出します。
call()の中身は作成したブロックの引数です。
ブロック定義時に||の中身をセットすると、それが引数として扱われます。
上記のコードの中では、引数に10がセットされ、ブロック内の|z|が|10|に変化します。

Procをインスタンスメソッドに渡す場合は、ブロックをメソッドに渡してから、以下ようにメソッド定義時に&をつけた引数を記入します。

proc_statement.rb
x = 1
y = 1

def output(&block)
  block.call * 10
end

puts output { x + y } # => 20

&はブロックをオブジェクトに変換します。つまり、上のコードの場合、ブロックをオブジェクトに変換し、それをblockという変数の中に格納しているわけです。
メソッド内には&を取り除いた引数をcallで呼び出して、ブロックを展開しています。
もちろん、すでにProcとなっているブロックを渡すこともできます。

proc_statement.rb
x = 1
y = 1

def output(&block)
    block.call * 10
end

block = Proc.new { x + y }
puts output &block # => 20

また、&はオブジェクトをブロックに変換することもできます。
したがって、以下のコードも可能です。

proc_statement.rb
x = 1
y = 1

def output
  # ブロックが引数に入ってきているので、yieldを使う。
  yield * 10
end

block = Proc.new { x + y }

# Procをブロックに変換して、メソッドに渡す。
puts output(&block) # => 20

Lambda

Lambdaには字面的に難解な雰囲気が漂いますが、Procの色違いのようなものです。
挙動が少しProcとは異なりますが、やることは基本同じことです。

lambda_statement.rb
x = 1
y = 1

block = lambda { |num| (x + y) * num }
puts block.call(10) # => 20

また、以下は上記のコードと同じように振る舞います。

lambda_another_statement.rb
x = 1
y = 1

block = ->(num) { (x + y) * num }
puts block.call(10) # => 20
lambda_another_statement.rb
x = 1
y = 1

block = -> { (x + y) * 10 }
puts block.call # => 20

Procと同様に、Lambdaの後ろにブロックを繋げ、必要であれば||に引数をセットすることができます。
callで呼び出し、ブロックの中に引数がセットされているのであれば、call(10)のようにして、引数に入れるものを指定できます。

Lambdaを引数としてメソッドに渡す場合は、以下のコードを参考にしてください。
基本的にProcと同じです。

lambda_statement.rb
# ブロックをオブジェクトに変換するだけならProcとまるっきり同じ。
# 引数の&は、ブロックをオブジェクトに変換し、変数blockに格納したと考えれば良い。
x = 1
y = 1

def output(&block)
  block.call * 10
end

puts output { x + y } # => 20
lambda_statement.rb
x = 1
y = 1

def output(&block)
  block.call * 10
end

block = lambda { x + y }
puts output &block
lambda_statement.rb
x = 1
y = 1

def output
  yield * 10
end

block = lambda { x + y }
puts output(&block)

ProcとLambdaの違い

ProcとLambdaはブロックをオブジェクトにしたもので、両者はProcクラスに分類されるという点で同等です。

difference.rb
proc = Proc.new { |x| x * 10 } # => ProcクラスのProc

lambda = lambda { |x| x * 10 } # => ProcクラスのLambda

puts proc.class # => Proc
puts lambda.class # => Proc

puts proc.lambda? # => False
puts lambda.lambda? # => True

LambdaはProcの色違いのようなものだと説明しましたが、それは、Procに属する毛色の違うProcである、というわけです。
反対に、Procに属するLambdaではないProcは、どう頑張ってもLambdaではありません。Lambdaになるのは、lambda{}と->{}の記法で作成されたProcのみです。lambdaかどうかを判断するには、lambda?メソッドが役に立ちます。

しかしながら、より厳密に言えば、それぞれは異なる挙動を行います。

ProcとLambdaの相違点を見分けるのはややこしいですし、例外もさまざまあるようなので、完全に理解する必要はないと思いますが、代表的な挙動の違いとして、returnの扱い方と、引数の扱い方の違いを説明します。

まずはそれぞれのreturnに対する挙動を確認します。

difference.rb
# Procの場合
def proc_output
  block = Proc.new { return 100 }
  called = block.call # => ブロックのスコープにあるreturnによってproc_outputの戻り値を返す
  return called * 10
end

# Lambdaの場合
def lambda_output
  block = lambda { return 100 }
  called = block.call
  return called * 10 # => メソッドのスコープにあるreturnによってlambda_outputの戻り値を返す
end

puts proc_output # => 100
puts lambda_output # => 1000

上記のコードから、
Procのブロック内にあるreturnは、callで評価された時に、メソッドのスコープを抜け出しますが、
Lambdaのブロック内にあるreturnは、callで評価された後、メソッドのスコープを抜け出さず、変数に格納され、メソッドのreturnによって戻り値になり、メソッドのスコープを抜け出しています。

もう1つのProcとLambdaの相違点は、引数の取り扱い方の違いにあります。

difference.rb
# Procの場合
proc = Proc.new { |x| x * 10 }
puts proc.call(5) # => 50
puts proc.call(5, 10) # => 50

# Lambdaの場合
lambda = lambda { |x| x * 10 }
puts lambda.call(5) # => 50
puts lambda.call(5, 10) # => wrong number of arguments (given 2, expected 1)

Procでは、余分に引数を渡していますが、正常に動いています。
対して、Lambdaでは、余分に引数を渡すとエラーが発生します。

Procは引数を多めに渡しても、少なめに渡しても、エラーを発生させることなく動いてくれます。
余分な引数は切り捨て、足りない分はnilとして許容するわけです。

しかし、Lambdaはそれを許しません。
より厳密に引数を受け取りたがります。

おさらいすると、ProcとLambdaには以下のような相違点があります。
1. returnの挙動が異なる
・Procでは、returnを行うと、メソッドを抜けて、returnの値を戻す。
・Lambdaでは、returnを行うと、returnした値をロジックに組み込んでメソッドの最後まで計算する。
2. 引数に対する厳格さ
・Procでは、設定した引数の数に対して渡す引数の数は幾つでもエラーにならない。
・Lambdaでは、設定した引数の数に対して、厳密な数の引数を対応させて渡さなければエラーになる。

このようなエラーがあるわけですが、どちらかというとLambdaを使った方が良いみたいです。
理由はProcのように引数に対して寛容すぎると、ヒューマンエラーで間違った数の引数を渡した時に、画面上ではエラーにならないので、そのエラー自体に気づきにくくなってしまうからです。
間違った時にはしっかりとエラーを吐いてくれるLambdaを利用した方が、プログラミングする側からすると、楽だとおもいます。

48
39
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
39