Ruby中的block, Proc和Lambda
Ruby有三種具有函數功能的東西,即一般的函數,Proc和lambda。
- Ruby的函數都是有名字的,每次呼叫函數必定會尋找receiver,再決定呼叫哪個物件的同名方法。
- Block包含一段待執行的程式碼,也是Ruby中唯一不是物件的東西。Block本身被設計成不能access的,只能在被包裝成物件後才能被access。
- Ruby可以將block存成proc物件,再於他處執行。
- Ruby也可以將block存成lambda,與proc有物件有些微不同。lambda表現更像是常見的匿名函數。
函數
Ruby定義函數很容易
def myfun
puts "myfun"
end
Object.myfun == myfun // true
以上也表明,如果不給定實例方法所處的位置,此函數就會自動綁定到頂層對象上,成為該對象的方法
Proc, lambda
Proc或lambda將一段程式碼包裝成物件,供別人使用
# 包裝
myproc = proc { puts "This is a Proc" }
mylamda = lambda { puts "This is a lambda" }
# 呼叫
myproc.call(param1, param2, ...)
mylambda.(param1, param2, ...)
附帶一提,下面這個寫法是完全等價的,這是Ruby convention,單行的程式碼就用{}
,多行就用do...end
myproc = proc do
puts "This is a Proc"
end
mylambda = lambda do
puts "This is a lambda"
end
Proc和lambda的差異主要有二,一是是否檢查參數,二是return的效果。
呼叫參數
# 指定要代入參數
myproc = proc {|s| puts "hihi"}
mylambda = lambda {|s| puts "hihi"}
# proc不檢查參數
myproc.call
#=> hihi
# lambda檢查參數
mylambda.call
#=> ArgumentError: wrong number of arguments
Return
Proc的return會跳出調用者的上下文,lambda的return只會跳出lambda自身的上下文
使用yield或call調用的差別
我們可以很簡單的使用yield關鍵字(不是方法)調用block
def hihi
puts "Start"
yield
puts "End"
end
hihi {puts "yooo"}
#=> Start
#=> yooo
#=> End
但這樣我們沒辦法access block。要access,可以包裝block成proc
def hihi(&p)
puts "Start"
p.call # p是一個proc物件,因為&p會將block轉成proc存入p
puts "End"
end
hihi {puts "yooo"}
#=> Start
#=> yooo
#=> End
雖然這樣造成一些效能損耗,但是你可以決定要在哪一個execution context執行該proc,這在建立DSL時特別有用。
# 可以使用Kernel.block_given?來判斷是否有block傳入,再做相應的措施
# File.open在有block和沒有block的時候有不同的行為,就是這樣實現的
def myfun(*args)
if block_given?
yield
puts "Block given"
else
puts "No block given"
end
end
可以看到以下兩種傳入block的效果是一模一樣的,而代block預設就是轉成Proc
def myfun(&p)
puts p
yield
end
myproc = proc { puts "use myproc" }
mylambda = lambda { puts "use mylambda" }
# 直接代block
myfun { puts "use block" }
#=> #<Proc:0x000000032330b8@(pry):115>
#=> use block
# 代已經定義過的Proc
myfun &myproc
#=> #<Proc:0x000000032ab770@(pry):116>
#=> use myproc
myfun &mylambda
#=> #<Proc:0x00000003235c00@(pry):117 (lambda)>
#=> use mylambda
其他寫法
# 下列語法可以產生Proc
Proc.new {|s| ...}
proc {|s| ...}
# 下列語法可以產生lambda
lambda {|s| ...}
-> s {...}