【Ruby】blockとprocとlambda 入門

Ruby の block と proc と lambda の違いをまとめました.

block とは

do ~ end または {} に囲われた部分.
method に処理を渡すために使われる.

# do-end block
10.times do |i|
  p i
end

# {} block
[:sun, :mon, :tue].map{ |w| p w.to_s.upcase }

# call block
def hoge
  if block_given?
    yield
  else
    p 'No block'
  end
end

hoge { p 'hogeeee!' }
#=> "hogeeee!"
hoge
#=> "No block"

method内でyieldを使うと,
渡されたblock(処理)を実行できる.
methodにblockが渡されたかどうかは,
block_given?で boolean が返ってくる.

Proc とは

Procは処理をinstance化して,
objectとして扱うためのもの.
Proc は Ruby の class クラスの object.
なので new method で instance化できる.
その際に処理をblockで渡す.
そのため proc のobjectをmethodに渡す事で,
blockと同様 method に処理を渡すことも出来る.
proc objectに対して call methodを呼ぶと,
proc objectの処理をよびだせる.

# instantiate Proc
# pass block to Proc#new
a = Proc.new { p 'hogeee!' }
b = Proc.new do |txt|
  p txt
end

# call proc
a.call #=> "hogeee!"
# call proc with args
b.call(:tarooo) #=> "tarooo"

method内でblock を procとして受け取る

method内でyield を使うと,
渡されたblockの処理を実行できました.
渡されたblockをmethod内でproc objectとして,
受け取る方法が存在します.
methodの引数定義の際に, 引数の名前の前に&をつけます.
&をつける引数は必ず, 「引数定義の最後の引数」です.

def hoge(&proc)
  p proc.class
  p proc.call
  'method end'
end

hoge { 'string in block' }
# Proc                      (p proc.class)
# "string in block"   (p proc.call)
#=> "method end"  (return value)

method内でproc を blockとして受け取る

さっきの逆で引数として渡したproc objectを,
blockとして受け取る.
procを引数に渡す際に &をつける事で可能.

def hoge
  yield # block の実行 proc objectの実行(= call methodの呼び出し)ではない
end

p = Proc.new { p 'string in proc' }
hoge(&p) # &をつける. 
# hoge methodは引数を定義してないのに
# proc objectを引数 **みたい** に(blockとして)渡せる.
#=> "string in proc"

様々なproc objectの生成方法

様々な方法でProc クラスのobjectを生成できる.

# symbolに対して to_proc method
p1 = :hoge.to_proc
p.class #=> Proc

# [method] method
# 引数にsymbolを渡すと, 
# receiverのobject(省略時はself)に対して
# 引数のsymbol名のmethodをproc化して返す.
def hoge
  p 'hogeee!!'
end

p2 = method(:hoge)
p2.class #=> Proc
p2.call #=> "hogeee!!"

lambdaとは

lamndaはKernel moduleの **method** である.

p Kernel.methods.select { |m| m.to_s.include?('lambda') }
#=> [:lambda]

そしてlambda methodの返り値は Proc クラスの インスタンス!!
Proc new された proc object と
lambdaで作られた proc objectには
引数, returnの扱いで差がある.
その違いについては今回は割愛.

s = lambda { p 'hogeee!' }
s.class #=> Proc
s.call #=> "hogeee!"

# alias of lambda 
s = -> { p :hoge }
s.class #=> Proc
s.call #=> "hoge"

おまけ

実はここまでの記事をちゃんと読み内容を理解していても
rails 開発で頻繁に出てくる次のコードを理解できない.
「method内でproc を blockとして受け取る」で紹介した,
procをmethodに引数みたいに渡す時に付けた& .
紹介した通りこいつはprocの頭につけるものだが,
symbolの頭につけるとそのsymbolをprocとして扱ってくれる.
(symbolにto_proc methodを呼び出したかのように)

['sun', 'mon', 'tue'].map(&:upcase)
#=> ["SUN", "MON", "TUE"]

# ['sun', 'mon', 'tue'].map(&(:upcase.to_proc))
# と同じ意味