Blog 已遷移 http://steventtud.com,logdown 版本不再更新,麻煩大家前往新網站觀看^^

Ruby 筆記:block / yield / Proc / lambda

block是什麼?

block類似Javascript的anonymous function,用這種方式寫作的好處是裡面的程式通常不用管實作,而是用的時候才去定義。包含了Proc和lambda兩種。

&block、block.call與yeild

&block用來輸入block
block.call 執行block
yeild 可以省略&block輸入,並且使用block.call的功能。

call block用在哪些地方?

不管是 each 或是 Rails 下的 layout 的 content_for :name 配上 yield :name 的用法,其實都是 call block 的實現就是

比較method / Proc / lambda

以下三種寫法是等價的。

1.method

def temp1( a , b = 123 , *argv , &block )
  #...a = 必要值,b = 預設值,*argv = 多出來的傳值,&block 送一個 block 進來

end

2.Proc

temp2 = Proc.new do | a , b = 123 , *argv , &block|
  #...

end
#=> #<Proc:0x000001021b9ab0@(irb):28>

可以縮寫成

temp2 = Proc.new { | a , b = 123 , *argv , &block|
  #...

}

3.lambda

temp3 = lambda do | a , b = 123 , *argv , &block|
  #...

end
#=> #<Proc:0x00000102aae260@(irb):29 (lambda)>

可以縮寫成

temp3 = lambda { | a , b = 123 , *argv , &block|
  #...

}

temp1是變數名稱,而temp2&3是匿名函數,就是一段尚未被執行的暫存程式碼,你可以把它丟到任何地方被執行,包括丟給其他的method。

call block的幾種方式

現在我們要執行block中的程式碼,所以又稱為call block。

Step1:先宣告兩個能夠執行block的function

def temp_b1( &block )
  block.call("im_tempB1")
end
def temp_b2
  yield("im_tempB2") #小刮號可省略

end

temp_b1 可以接受一個 block ,也就是接受一個 Proc 或 lambda 的程式進來,然後執行它,temp_b2 是 temp_b1 的縮寫,直接用 yield 去執行 block,兩段語法完全等價

Step2:宣告兩個block

block1 = lambda{|x| puts "block1 #{x}"}
block2 = Proc.new{|x| puts "block2 #{x}"}

Step3:把block丟進function中執行

第一種方法:正規的call block方法

temp_b1 do |x|
  puts "block0 #{x}"
end
#=> "block0 im_tempB1"

中間這一段puts "block0 #{x}"把我們宣告過的 block_x 丟到 method 中。

第二種方法

temp_b1(&block1)
#=> "block1 im_tempB1"
temp_b2(&block2)
#=> "block2 im_tempB2"

第一種方法的temp_b1 do |x|這段其實等同於temp_b2(&block2)這段的"縮寫",省略了傳入的&block。

lambda和Proc不同之處

Proc 和 lambda 最大的不同點有二。

第一個不同之處:傳入值

現在有兩個block,輸入都是x和y。

block_s1 = lambda{|x,y| puts [x.class,y.class]}
block_s2 = Proc.new{|x,y| puts [x.class,y.class]}

來測試一下lambda

block_s1.call(1,2,3,4,5) #=> ERROR : ArgumentError: wrong number of arguments (5 for 2)

block_s1.call(1,2) #=> OK : Fixnum \n Fixnum

block_s1.call() #=> ERROR : ArgumentError: wrong number of arguments (0 for 2)

呼叫時輸入值的數量要與宣告的時候相等才可以呼叫。
接著來測試使用Proc宣告的block。

block_s2.call(1,2,3,4,5) #=> OK : Fixnum \n Fixnum

block_s2.call(1,2) #=> OK : Fixnum \n Fixnum

block_s2.call() #=> OK : NilClass \n NilClass

當傳入值不符合宣告時的參數數量的時候也不會產生ERROR訊息。

第二個不同之處:return

(此段摘自david50407大的解說。)

def a
    func = Proc.new { return "proc" }
    func.call
    return "def"
end

def b
    func = lambda { return "lambda" }
    func.call
    return "def"
end
a 
#=> "proc"


b 
#=> "def"

不管 lambda 怎麼 return 都不會影響後續,但是 Proc 會搶過來 return 整個 function。

延伸閱讀

Ruby Block, Proc and Lambda
method / block / yield / Proc / lambda 全面解釋
Block與Yield解釋