何时使用lambda,何时使用Proc.new?


336

在Ruby 1.8中,一方面proc / lambda之间存在细微的差异,另一方面Proc.new

  • 这些差异是什么?
  • 您可以就如何决定选择哪一项提供指导吗?
  • 在Ruby 1.9中,proc和lambda是不同的。这是怎么回事?

3
另请参见:Matz和Flanagan撰写的Ruby Programming Language一书,它全面涵盖了该主题。proc的行为类似于块-yield语义,而lambda的行为类似于方法-方法调用语义。还返回,休息等。所有人在procs lambdas中都表现出差异
Gishu 2010年


您已经接受了仅说明proc和lambda有什么区别的答案,而您的问题的标题是何时使用这些内容
Shri

Answers:


378

使用lambda创建的proc 和使用创建的proc之间的另一个重要但微妙的区别Proc.new是它们处理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"

由于这种令人惊讶的行为(以及更少的键入),我在制作proc时倾向于使用lambdaover Proc.new


12
然后还有proc方法。这只是简写Proc.new吗?
panzi 2010年

6

4
@mattdipasquale在我的测试中,关于返回语句的proc行为lambda与不喜欢的行为相同Proc.new。这意味着红宝石文档不正确。
开尔文

31
@mattdipasquale对不起,我只有一半。proc行为类似于lambda1.8,但行为类似于Proc.new1.9。请参阅Peter Wagenet的答案。
开尔文

55
为什么这种“令人惊讶”的行为?A lambda是匿名方法。由于它是一个方法,因此它返回一个值,并且调用它的方法可以使用它所需的任何值,包括忽略它并返回一个不同的值。A Proc就像粘贴代码段一样。它不像一种方法。因此,当在中发生返回时Proc,这只是调用它的方法代码的一部分。
Arcolye 2012年

96

为了进一步说明:

Joey说的退货行为Proc.new令人惊讶。但是,当您认为Proc.new的行为类似于块时,这并不奇怪,因为这正是块的行为方式。另一方面,lambas的行为更像方法。

这实际上解释了为什么Procs在处理Arity(参数数量)时灵活,而Lambda却不灵活。块不需要提供所有参数,而方法需要(除非提供了默认值)。尽管在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(OP)关于Procs和lambda在Ruby 1.9中具有相同的含义是不正确的。我已验证他们仍然保持上述指定的行为1.8。

break在Procs或lambda中,语句实际上没有多大意义。在Procs中,中断将使您从已经完成的Proc.new返回。而且,从lambda中断并没有任何意义,因为它本质上是一种方法,并且您永远也不会从方法的顶层中断。

nextredoraise在Procs和Lambda中的行为相同。两者均retry不允许,并且将引发异常。

最后,proc永远不要使用该方法,因为它会导致不一致并且行为异常。在Ruby 1.8中,它实际上返回一个lambda!在Ruby 1.9中,此问题已修复,并返回Proc。如果要创建Proc,请坚持使用Proc.new

有关更多信息,我强烈推荐O'Reilly的Ruby编程语言,它是我获得大部分此类信息的来源。


1
“”“但是,当您认为Proc.new的行为类似于块时,这并不奇怪,因为这正是块的行为方式。”“” <-块是对象的一部分,而Proc.new创建对象。lambda和Proc.new都创建一个类为Proc的对象,为什么要进行差异处理?
减弱

1
如红宝石2.5的,break从特效加注LocalJumpError,而break从lambda表达式行为就像returnreturn nil)。
坂上真纱

43

我发现这个页面,显示之间有什么区别Proc.newlambda是。根据页面,唯一的区别是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“你好”,“世界”
=>“ helloworld”
irb(main):004:0> p.call“你好”,“世界”
=>“ helloworld”
irb(main):005:0> l.call“你好”
ArgumentError:参数数量错误(1表示2)
    来自(irb):1
    来自(irb):5:在`call'中
    来自(irb):5
    从:0
irb(main):006:0> p.call“你好”
TypeError:无法将nil转换为String
    来自(irb):2:in'+'
    来自(irb):2
    来自(irb):6:in'call'
    来自(irb):6
    从:0

该页面还建议使用lambda,除非您特别希望容错行为。我同意这种观点。使用lambda似乎更简洁一些,但由于差异不大,在一般情况下似乎是更好的选择。

至于Ruby 1.9,对不起,我还没有研究过1.9,但是我不认为他们会做很多改变(尽管请不要相信我,看来您已经听说了一些更改,所以我可能在那里错了。


2
proc的返回值也不同于lambda。
2013年

“”“ Proc.new会将缺少的参数转换为nil”“”“ Proc.new也会忽略多余的参数(当然lambda会因为错误而抱怨)。
减弱

16

Proc年龄较大,但是return的语义对我来说是非常违反直觉的(至少在我学习语言时),因为:

  1. 如果您使用proc,则很可能使用某种功能范例。
  2. Proc可以退出封闭范围(请参阅先前的响应),这基本上是goto,本质上是高度不起作用的。

Lambda在功能上更安全,更容易推理-我始终使用它而不是proc。


11

对于细微的区别,我不能说太多。但是,我可以指出,Ruby 1.9现在允许用于lambda和block的可选参数。

这是1.9以下的稳定lambda的新语法:

stabby = ->(msg='inside the stabby lambda') { puts msg }

Ruby 1.8没有该语法。常规的声明块/ lambda的方法也不支持可选的args:

# 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,请查看本文(无耻的自我推广)。


非常需要lambda中的可选参数,我很高兴他们在1.9中添加了它。我假设块也可以具有可选参数(在1.9中)?
mpd

您不是在块中展示默认参数,而只是在lambda上展示
iconoclast

11

简短的答案:重要的是做什么return:lambda自身退出,而proc自身退出以及调用它的函数。

还不清楚为什么要使用它们。lambda是我们期望在功能编程意义上应该做的事情。它基本上是一个匿名方法,当前范围自动绑定。在这两种中,lambda是您可能应该使用的一种。

另一方面,Proc对于实现语言本身确实很有用。例如,您可以使用它们来实现“ if”语句或“ for”循环。在proc中找到的任何返回值都将从调用它的方法中返回,而不仅仅是“ if”语句。这就是语言的工作方式,“ if”语句的工作方式,所以我猜Ruby是在幕后使用它的,他们只是公开了它,因为它看起来很强大。

只有在创建新的语言结构(如循环,if-else结构等)时,才真正需要此功能。


1
“ lambda退出自身,而proc退出自身并调用它的函数”是完全错误的,是一个非常普遍的误解。proc是一个闭包,并从创建它的方法中返回。在页面其他地方查看我的完整答案。
ComDubh

10

看到它的一个好方法是,lambda在它们自己的范围内执行(就像它是一个方法调用一样),而Procs可能被视为与调用方法内联执行,至少这是决定使用哪一个方法的好方法在每种情况下。


8

我没有注意到关于问题的第三个方法“ 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

1
Matz表示他计划弃用它,因为proc和Proc.new返回不同的结果会令人困惑。在1.9中,它们的行为相同(proc是Proc.new的别名)。eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin 2010年

@banister:proc在1.8中返回了一个lambda;现在已经修复了在1.9中返回proc的问题-但是这是一个重大的改变; 因此,不建议再使用
Gishu 2010年

我认为镐在脚注中指出了proc已被有效描述的某处。我没有确切的页码。
dertoni 2010年

7

Ruby中的闭包很好地概述了Ruby中的块,lambda和proc如何在Ruby中工作。


我读完“一个函数不能接受多个块-违反了闭包可以作为值自由传递的原则”后,我停止阅读此书。块不是闭包。过程是,一个函数可以接受多个proc。
ComDubh

5

lambda可以像其他语言一样正常工作。

有线Proc.new是令人惊讶和混乱的。

return在proc中创建的语句Proc.new不仅会返回自身的控制权,还会返回包含它的方法的控制权。

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"

顺便说一句,proc在Ruby 1.8中创建一个lambda,而在Ruby 1.9+中的行为类似于Proc.new,这确实令人困惑。


3

要详细说明“手风琴家”的回应:

注意,Proc.new通过传递一个块来创建一个proc。我相信这lambda {...}被解析为一种文字,而不是通过块的方法调用。 return从附加到方法调用的代码块中返回的内容将从方法而不是代码块返回,并且Proc.new情况就是这种情况的一个例子。

(这是1.8。我不知道这如何转换为1.9。)


3

我有点迟了,但是关于Proc.new注释中没有提到的一件伟大但鲜为人知的事情。如通过文档

Proc::new可以仅在具有附加块的方法中在没有块的情况下调用,在这种情况下,该块将转换为Proc对象。

也就是说,Proc.new让链式产生方法:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

有趣的是,它的作用与在中声明&block参数相同def,但不必在def arg列表中声明。
jrochkind

2

这是值得强调的是,return从词法封闭方法PROC的回报,即在其中创建PROC的方法调用该进程内的方法。这是procs的闭合属性的结果。因此,以下代码不输出任何内容:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

尽管proc foobar是在proc中执行的,但它是在in中创建的foo,因此return存在出口foo,而不是出口foobar。正如查尔斯·考德威尔(Charles Caldwell)上文所述,它具有GOTO的感觉。在我看来,return在按词法上下文执行的块中很好,但是在不同上下文中执行的proc中使用时则不太直观。


1

行为上的区别return是恕我直言,两者之间最重要的区别。我也更喜欢lambda,因为它的键入比Proc.new少:-)


2
更新:现在可以使用创建proc proc {}。我不确定何时生效,但是(比)键入Proc.new容易(略)。
aceofbassgreg
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.