Proc 与 lambda
本文截取一段 ruby doc 官网 Proc 的内容并翻译,试图更好的理解和总结 Proc(non-lambda) 和 lambda 的区别和使用场景。
Lambda and non-lambda semantics
Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:
In lambdas,
return
means exit from this lambda;In regular procs,
return
means exit from embracing method (and will throwLocalJumpError
if invoked outside the method);In lambdas, arguments are treated in the same way as in methods: strict, with
ArgumentError
for mismatching argument number, and no additional argument processing;Regular procs accept arguments more generously: missing arguments are filled with
nil
, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments.p = proc {|x, y| "x=#{x}, y=#{y}" } p.call(1, 2) #=> "x=1, y=2" p.call([1, 2]) #=> "x=1, y=2", array deconstructed p.call(1, 2, 8) #=> "x=1, y=2", extra argument discarded p.call(1) #=> "x=1, y=", nil substituted instead of error l = lambda {|x, y| "x=#{x}, y=#{y}" } l.call(1, 2) #=> "x=1, y=2" l.call([1, 2]) # ArgumentError: wrong number of arguments (given 1, expected 2) l.call(1, 2, 8) # ArgumentError: wrong number of arguments (given 3, expected 2) l.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2) def test_return -> { return 3 }.call # just returns from lambda into method body proc { return 4 }.call # returns from method return 5 end test_return # => 4, return from proc
直接翻译:
Procs 有两种形式:lambda
和 non-lambda (regular procs)
。它们的不同之处在于:
- 在
lambda
中,return
表示从该lambda
退出; - 在
常规 proc
中,return
表示从该方法退出(如果在方法外部调用,将抛LocalJumpError
); - 在
lambda
中,对参数的处理方式与方法相同:strict
(严格),使用ArgumentError
表示参数编号不匹配,并且不对附加参数进行处理; -
常规 proc
更慷慨地接受参数:缺少的参数填充为nil
,如果proc
具有多个参数,则解构单个Array
参数,并且在附加参数上不引发错误。
个人更白话的理解:
常规(非lambda)proc | lambda |
---|---|
return 退出整个 method
|
return 仅从 lambda 内部退出 |
慷慨地接受参数:缺少的参数填充为 nil ,如果 proc 具有多个参数,则解构单个 Array 参数,并且在附加参数上不引发错误 |
strict(严格),使用 ArgumentError 表示参数编号不匹配,并且不对附加参数进行处理 |
Lambdas are useful as self-sufficient functions, in particular useful as arguments to higher->order functions, behaving exactly like Ruby methods.
直接翻译
Lambda
作为自给自足的函数很有用,特别是作为高阶函数的参数时,与Ruby方法完全一样。ps: 这句话可以帮助牢记,lambda
的检查参数和 return
lambda
自身函数的性质。
Procs are useful for implementing iterators:
def test [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 } # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ end
Inside map, the block of code is treated as a regular (non-lambda) proc, which means that the internal arrays will be deconstructed to pairs of arguments, and return will exit from the method test. That would not be possible with a stricter lambda.
代码执行结果:
2.4.4 :005 > def test
2.4.4 :006?> [[1, 2], [3, 4], [5, 6]].map {|a, b| return a if a + b > 10 }
2.4.4 :007?> # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2.4.4 :008?> end
=> :test
2.4.4 :009 > test
=> 5
直接翻译
Procs
在迭代中很有用,在 map
内部,代码块被视为常规(非lambda)proc
,这意味着内部数组将被解构为成对的参数,而 return
将退出方法 test
。如果使用更严格的 lambda
,将不可能做到。
个人白话理解
Procs
这种常规(非lambda)proc
方式,在 map
这种迭代器中是很好的,上图看执行结果,可以实现在 map
内部直接 return
整个 test
方法。这比使用 lambda
更符合我们的代码意图。
The only exception is dynamic method definition: even if defined by passing a non-lambda proc, methods still have normal semantics of argument checking.
class C define_method(:e, &proc {}) end C.new.e(1,2) #=> ArgumentError C.new.method(:e).to_proc.lambda? #=> true
This exception ensures that methods never have unusual argument passing conventions, and makes it easy to have wrappers defining methods that behave as usual.
class C def self.def2(name, &body) define_method(name, &body) end def2(:f) {} end C.new.f(1,2) #=> ArgumentError
The wrapper def2 receives body as a non-lambda proc, yet defines a method which has normal semantics.
直接翻译
唯一的例外是动态方法定义:即使通过传递 非lambda proc
进行定义,方法仍然具有参数检查的常规语义。此异常可确保方法永远不会具有异常的参数传递约定,并使包装器轻松定义照常运行的方法。包装器 def2
将主体作为 非lambda proc
接收,但定义了一种具有正常语义的方法。
个人白话理解
例外的是,即使通过非 lambda 的 proc 去动态定义 method,也会按照 lambda 的方式严格检查参数,这保证了动态定义方法的正常运行,不会出现和正常的 method
不同的行为。
另外我也从 Rails 的 scope 为什么用 lambda?Proc 与 lambda 有什么不同之处? 这篇博文得到了些启发,我在记住这两种 Procs
不同点的时候,时常忘记到底是 常规 proc
检查参数还是 lambda
,但如果能从「Rails 的 scope 为什么用 lambda?」这个问题来思考的话,就更加的能记忆清楚了。Rails
的 scope
之所以用 lambda
就是因为 lambda
会严格检查参数可以避免 Proc
对于「缺少的参数填充为 nil
」的行为导致 sql
查询有误。
总结
由上面分析可以看出,Proc(non-lambda)
和 lambda
都有各自的用武之处,通过对代码的预期不同,来灵活使用从而达到准确实作功能的意图。从这点上来讲,深入了解两种 Procs
确实有助于我们写出更合适的代码并理解代码本身。