在規則引擎中,Ruby 的閉包使用特別頻繁,而且有 block,Proc和 lambda 等后幾種形式的用法,很讓人困惑。為了深入理解代碼,再次認真學習了一下 Ruby 的閉包,特別是 block,proc 和 lambda 幾種用法的異同,這次的周記就和大家分享一下心得。
閉包是 Ruby 相對其它語言特別優勢之一,很多語言有閉包,但是唯有 Ruby 把閉包的優勢發揮得淋漓盡致。Ruby 的哲學之一:同一個問題現實中有多種解決方法,所以 Ruby 中也有多種解法,所以閉包的使用也有多種方法。
先看一個代碼的例子:
Example 1:
def foo1
yield
end
def foo2(&b)
b.call if b
end
foo1 { puts "foo1 in block" }
foo2 { puts "foo2 in block" }
proc = Proc.new { puts "foo in proc" }
foo1(&proc)
foo2(&proc)
lambda_proc = lambda { puts "foo in lambda" }
foo1(&lambda_proc)
foo2(&lambda_proc)
輸出:
》foo1 in block
》foo2 in block
》foo in proc
》foo in proc
》foo in lambda
》foo in lambda
大家是不是有些困惑,首先是方法 foo1 和 foo2 都能接收閉包,它們這兩種寫法有什么區別,然后是作為參數的三種閉包 block,proc和 lambda有什么區別。
1. yield 和 block call 的區別
yield 和 block call 兩種都能實現運行閉包,從實際運行效果來說,區別不大。其區別主要在于:
1.1 閉包的傳遞和保存
因為閉包已經傳遞到參數里,所以可以繼續傳遞或保存起來,例如:
Exampl 2:
class A
def foo1(&b)
@proc = b
end
def foo2
@proc.call if @proc
end
end
a = A.new
a.foo1 { puts "proc from foo1" }
a.foo2
1.2 性能
yield不是方法調用,而是 Ruby 的關鍵字,yield 要比 block call 比快 1 倍左右。
2. block 和 proc, lambda 的區別
很簡單直接,引入 proc 和 lambda 就是為了復用,避免重復定義,例如在 example 1 中,使用 proc 變量存儲閉包,避免重復定義兩個 block 。
3. proc 和 lambda 的區別
這大概是最讓人困惑的地方,從 Example 1 的行為上看,他們的效果是一致的,為什么要用兩種不同的表達方式。
proc = Proc.new { puts "foo in proc" }
lambda_proc = lambda { puts "foo in lambda" }
確實,對于簡單的情況,比如 Example 1的情況,他們的行為是一致的,但是主要在兩個地方有明顯差異:
1.1 參數檢查
還是例子說話 Example 3:
def foo
x = 100
yield x
end
proc = Proc.new { |a, b| puts "a is #{a.inspect} b is #{b.inspect}" }
foo(&proc)
lambda_proc1 = lambda { |a| puts "a is #{a.inspect}" }
foo(&lambda_proc1)
lambda_proc2 = lambda { |a, b| puts "a is #{a.inspect} b is #{b.inspect}" }
foo(&lambda_proc2)
輸出
》a is 100 b is nil
》a is 100
》ArgumentError: wrong number of arguments (1 for 2)
…
可見,proc 不會對參數進行個數匹配檢查,而 lambda 會,如果不匹配還會報異常,所以安全起見,建議優先用 lambda。
1.2 返回上層
還是例子說話 Example 4:
def foo
f = Proc.new { return "return from foo from inside proc" }
f.call # control leaves foo here
return "return from foo"
end
def bar
f = lambda { return "return from lambda" }
puts f.call # control does not leave bar here
return "return from bar"
end
puts foo
puts bar
輸出
》return from foo from inside proc
》return from lambda
》return from bar
可見,proc 中,return 相當于執行了閉包環境里的 return,而 lambda 只是返回call 閉包所在環境。
總結:閉包是 Ruby 的強大特性,它的幾種表達方式block,proc 和 lambda有細微差別,要用好它需要對其深入理解。