在 Ruby 1.8 中,proc/lambda 与 Proc.new
有微妙的差别。
- 这些差别是什么?
- 你能提供决定使用哪个的指南吗?
- 在 Ruby 1.9 中,proc 和 lambda 是不同的。怎么回事?
在 Ruby 1.8 中,proc/lambda 与 Proc.new
有微妙的差别。
lambda
和 Proc.new
创建的proc之间还有一个重要但微妙的区别,就是它们如何处理 return
语句:
lambda
创建的 proc 中,return
语句只会从该 proc 返回Proc.new
创建的 proc 中,return
语句则更为出人意料:它不仅会从该 proc 返回,而且还会从包含该 proc 的方法中返回!下面是 lambda
创建的 proc 中 return
的实际应用。它的行为可能符合你的预期:
def whowouldwin
mylambda = lambda {return "Freddy"}
mylambda.call
# mylambda gets called and returns "Freddy", and execution
# continues on the next line
return "Jason"
end
whowouldwin
#=> "Jason"
现在我们来看一个由 Proc.new
创建的 proc,它的 return
做相同的事情。你将要看到一个 Ruby 违反了备受赞誉的最小惊奇原则的情况:
def whowouldwin2
myproc = Proc.new {return "Freddy"}
myproc.call
# myproc gets called and returns "Freddy",
# but also returns control from whowhouldwin2!
# The line below *never* gets executed.
return "Jason"
end
whowouldwin2
#=> "Freddy"
由于这种令人惊讶的行为(以及更少的输入),在创建procs时,我倾向于使用lambda
而不是Proc.new
。
proc
的方法,它只是Proc.new
的简写形式吗? - panziproc
的行为类似于lambda
,但在1.9中则类似于Proc.new
。请参考Peter Wagenet的回答。 - Kelvinlambda
是一个匿名函数。由于它是一个函数,所以它会返回一个值,调用它的方法可以对该值进行任何操作,包括忽略它并返回不同的值。Proc
类似于粘贴代码片段。它不像函数一样运行。因此,当在 Proc
中发生返回时,那只是调用它的方法代码的一部分。 - ArcolyeProc
不会抛出错误,而lambda
会抛出“参数数量错误”的错误。 - bigpotato为了进一步澄清:
Joey说Proc.new
的返回行为令人惊讶。然而,当您考虑到Proc.new的行为类似于块时,这并不令人惊讶,因为这正是块的行为方式。另一方面,lambdas的行为更像方法。
这实际上解释了为什么Procs在参数数量(arity)方面很灵活,而lambdas则不是。块不需要提供它们的所有参数,但方法需要(除非提供默认值)。在Ruby 1.8中,虽然不能为lambda参数提供默认值,但现在在Ruby 1.9中支持使用另一种lambda语法来实现(正如webmat所指出的):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
Michiel de Mare(即原作者)对于Procs和lambda在Ruby 1.9中的arity表现不正确。我验证它们仍然保持与上述1.8版本相同的行为。
break
语句在Procs和lambdas中都没有太多意义。在Procs中,break将会从已经完成的Proc.new中返回你;而在lambda中从方法的最高层级跳出是没有任何意义的。
next
、redo
和raise
在Procs和lambdas中的表现是相同的。而retry
在两者中均不允许,并会引发异常。
最后,proc
方法不应该使用,因为它不一致并具有意外的行为。在Ruby 1.8中,它实际上返回一个lambda!在Ruby 1.9中,这已经得到了修复,它返回一个Proc。如果要创建Proc,请使用Proc.new
。
更多信息,我强烈推荐O'Reilly的《Ruby编程语言》这本书,这是我大部分信息的来源。
Procs
中的 break
抛出 LocalJumpError
,而 lambdas
中的 break
的行为就像 return
,也就是返回 nil
。 - Masa SakanoProc.new
和lambda
之间的区别。根据该页面,唯一的区别是lambda对其接受的参数数量非常严格,而Proc.new
会将缺少的参数转换为nil
。下面是一个IRB会话的例子,说明了它们之间的区别:
irb(main):001:0> l = lambda { |x, y| x + y } => #<Proc:0x00007fc605ec0748@(irb):1> irb(main):002:0> p = Proc.new { |x, y| x + y } => #<Proc:0x00007fc605ea8698@(irb):2> irb(main):003:0> l.call "hello", "world" => "helloworld" irb(main):004:0> p.call "hello", "world" => "helloworld" irb(main):005:0> l.call "hello" ArgumentError: wrong number of arguments (1 for 2) from (irb):1 from (irb):5:in `call' from (irb):5 from :0 irb(main):006:0> p.call "hello" TypeError: can't convert nil into String from (irb):2:in `+' from (irb):2 from (irb):6:in `call' from (irb):6 from :0该页面还建议除非您特别需要容错行为,否则请使用lambda。我同意这种观点。使用lambda似乎更加简洁,并且由于差异微不足道,在普通情况下它似乎是更好的选择。
Proc已经有些老了,但是它的返回语义对我来说非常反直觉(至少在我学习这门语言时是这样),因为:
Lambda在功能上更加安全和易于理解 - 我总是使用它来代替proc。
我无法深入探讨细微的差异。但我可以指出,Ruby 1.9现在允许在lambda和block中使用可选参数。
以下是1.9版本下stabby lambdas的新语法:
stabby = ->(msg='inside the stabby lambda') { puts msg }
Ruby 1.8没有这种语法。常规方法声明块/lambda也不支持可选参数:
# under 1.8
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'| puts msg }
然而,Ruby 1.9版本即使使用旧语法也支持可选参数:
l = lambda { |msg = 'inside the regular lambda'| puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez
如果你想为Leopard或Linux构建Ruby1.9,请查看这篇文章(无耻的自我推广)。
简短回答:关键在于return
的作用:lambda 从自身返回,而proc 不仅从自身返回,还可以返回调用它的函数。
不太清楚的是为什么要使用它们中的哪一个。从函数式编程的角度来看,lambda 是我们预期的做法。它基本上是一个带有自动绑定当前作用域的匿名方法。在这两种情况下,您应该使用 lambda。
另一方面,Proc 对于实现语言本身非常有用。例如,您可以使用它们实现“if”语句或“for”循环。在 proc 中发现的任何返回将从调用它的方法返回,而不仅仅是“if”语句。这就是语言的工作方式,以及“if”语句的工作方式,因此我猜测 Ruby 在内部使用了这个,并且他们只是公开了它,因为它似乎很强大。
只有在创建新的语言结构(如循环、if-else 结构等)时才真正需要它。
我没有在问题的第三种方法“proc”上注意到任何评论,这种方法已被弃用,但在1.8和1.9中处理方式不同。
下面是一个相当详细的示例,可以轻松地看到这三个类似调用之间的差异:
def meth1
puts "method start"
pr = lambda { return }
pr.call
puts "method end"
end
def meth2
puts "method start"
pr = Proc.new { return }
pr.call
puts "method end"
end
def meth3
puts "method start"
pr = proc { return }
pr.call
puts "method end"
end
puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
Ruby中的闭包是一个很好的概述,介绍了在Ruby中如何使用块、lambda和proc。
lambda的作用和其他语言一样。
Proc.new
有些令人惊讶和困惑。
Proc.new
创建的proc中的return
语句不仅会从自身返回控制,而且还会从封闭它的方法中返回。
def some_method
myproc = Proc.new {return "End."}
myproc.call
# Any code below will not get executed!
# ...
end
你可以认为Proc.new
像块一样将代码插入到包围它的方法中。
但是Proc.new
创建了一个对象,而块是对象的一部分。
另外,lambda和Proc.new
之间还有另一个区别,就是它们对于(错误的)参数处理方式不同。
lambda会抱怨它,而Proc.new
会忽略额外的参数或将缺少的参数视为nil。
irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
from (irb):21:in `block in irb_binding'
from (irb):25:in `call'
from (irb):25
from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
from (irb):47:in `block in irb_binding'
from (irb):49:in `call'
from (irb):49
from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"
顺便提一下,在 Ruby 1.8 中,proc
创建一个 lambda 函数,而在 Ruby 1.9+ 中则像 Proc.new
一样运行,这真的很令人困惑。