Ruby中proc和lambda有什么区别?

224

那么,在何时使用其中一种而不是另一种呢?


1
除了jtbandes的答案之外,在proclambda中,return语句返回的结果也有所不同。 - Ken Bloom
6
下面是一篇不错的博客,讲述了有关"block"和"lambda"之间的区别。链接为 http://awaxman11.github.io/blog/2013/08/05/what-is-the-difference-between-a-block/。 - Arup Rakshit
3
这里是更详细的回答:https://dev59.com/pnVD5IYBdhLWcg3wXaid - Dan K.K.
@ArupRakshit - 链接现在已经失效了。 - Zain Arshad
8个回答

305

其中一个区别在于它们处理参数的方式不同。使用proc {}Proc.new {}创建一个proc是等效的。然而,使用lambda {}会给你一个检查传递给它的参数数量的proc。来自ri Kernel#lambda

Proc.new相同,但返回的Proc对象在调用时检查传递的参数数量。

一个例子:

p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
p.call 1, 2 # => 5
p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass
p.call 1, 2, 3 # => 5
l = lambda {|a, b| puts a**2+b**2 } # => #<Proc:0x15016c@(irb):5 (lambda)>
l.call 1, 2 # => 5
l.call 1 # => ArgumentError: wrong number of arguments (1 for 2)
l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2)

另外,正如Ken指出的那样,在lambda中使用return将返回该lambda的值,但在proc中使用return则会从封闭块中返回。

lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return

因此,对于大多数快速使用情况,它们是相同的,但如果您想要自动严格的参数检查(这有时也可以帮助调试),或者如果您需要使用 return 语句来返回 proc 的值,请使用 lambda


14
能否这样说,Lambda 很像方法(会检查参数和返回值并从中返回),而 Proc 很像块(不会检查参数且 return 语句将从包含它们的方法或 Lambda 中返回)? - pedz
11
迄今为止,我浏览了无数个网站和文章,但似乎没有人谈论过Procs、方法和Lambda的实用性。每个解释都只提供细节上的区别,例如返回值等方面,但没有说明为什么这很重要。现在,我不得不认为这是Ruby中的设计混乱。 - ankush981
1
@ankush981 我同意参数的严格性应该是统一的(例如始终严格)。但是返回值的差异似乎有其存在的意义,我可以想象它们有其使用案例。为什么你认为这是一个“设计混乱”呢?尽管如此,它肯定没有很好地记录下来。 - Joel Blum

102

Proc和Lambda之间的真正区别与控制流关键字有关。我说的是returnraisebreakredoretry等控制词。假设您在一个proc中有一个return语句。当您调用这个proc时,它不仅会退出该proc,还将从封闭方法返回,例如:

def my_method
  puts "before proc"
  my_proc = Proc.new do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method

shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc

由于我们在调用proc时,其中的return语句将我们跳出方法,所以方法中的最后一个puts语句从未执行。但是,如果我们将我们的proc转换成lambda表达式,我们会得到以下结果:

def my_method
  puts "before proc"
  my_proc = lambda do
    puts "inside proc"
    return
  end
  my_proc.call
  puts "after proc"
end

my_method
shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb
before proc
inside proc
after proc

lambda 内的返回语句只会退出 lambda 本身,而封闭的方法将继续执行。控制流关键字在 procs 和 lambdas 中的处理方式是它们之间的主要区别。


它是唯一一个控制流关键字吗?它的工作方式与其他关键字不同吗? - undefined

27

只有两个主要的区别。

  • 首先,lambda 检查传递给它的参数数量,而 proc 不会。这意味着,如果您向 lambda 传递错误数量的参数,它将抛出错误,而 proc 将忽略意外的参数并将 nil 分配给缺少的参数。
  • 其次,当 lambda 返回时,它将控制权传递回调用方法;而当 proc 返回时,它立即返回,不返回到调用方法。

为了看看它是如何工作的,请参阅下面的代码。第一个方法调用一个 proc;第二个方法调用一个 lambda

def batman_ironman_proc
  victor = Proc.new { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_proc # prints "Batman will win!"

def batman_ironman_lambda
  victor = lambda { return "Batman will win!" }
  victor.call
  "Iron Man will win!"
end

puts batman_ironman_lambda # prints "Iron Man will win!"

看看proc如何说“Batman will win!”,这是因为它立即返回,而不会回到batman_ironman_proc方法。

然而,我们的lambda被调用后会回到方法中,所以该方法将返回它评估的最后一行代码:“Iron Man will win!”


24

# Proc 示例

p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              # The '&' tells ruby to turn the proc into a block 

proc = Proc.new { puts "Hello World" }
proc.call

# Lambda实例

lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call           

Proc 和 Lambda 之间的区别

在我深入讲解 Proc 和 Lambda 之间的区别之前,需要提到它们都是 Proc 对象。

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }

proc.class # returns 'Proc'
lam.class  # returns 'Proc'

然而,Lambda是Proc的不同“风味”。当返回对象时,这种微小的区别得以展现。

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'

1. Lambdas 检查参数数量,而 procs 不检查

lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
lam.call(2)                    # prints out 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)

相比之下,procs不关心它们是否被传递了错误数量的参数。

proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2)                   # prints out 2
proc.call                      # returns nil
proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments

2. Lambdas和procs在处理'return'关键字时有所不同

在lambda内部使用'return'会触发lambda代码外部的代码执行。

def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # calling lambda_test prints 'Hello World'

'return'在proc内触发执行该proc的方法外的代码

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # calling proc_test prints nothing

回答你的另一个问题,何时使用哪个?我会依照 @jtbandes 的建议。

对于大多数快速使用场景它们是相同的,但如果你需要自动严格参数检查(这有时也可以帮助调试),或者你需要使用 return 语句来返回 proc 的值,则使用 lambda。

原始帖子在这里


4
一般来说,Lambda比Proc更直观,因为它们更类似于方法。它们对元数非常严格,并且在调用return时简单地退出。因此,许多Ruby程序员首选Lambda,除非他们需要Proc的特定功能。 Proc:class Proc的对象。像块一样,在定义它们的作用域中进行评估。 Lambda:也是class Proc的对象,但与常规Proc有微妙的不同。像块和Proc一样,它们是闭包,因此在定义它们的作用域中进行评估。
创建Proc
a = Proc.new { |x| x 2 }

创建lambda函数

b = lambda { |x| x*2 }


1
a = proc { |x| x 2 } is the same as a = Proc.new { |x| x 2 } - lacostenycoder

2

这里有另一种理解方式。

一个块是附加到对象方法调用的调用中的代码块。在下面的示例中,self是匿名类的实例,该类继承自Rails框架(它本身包含许多助手模块)中的ActionView::Base。card是我们在self上调用的方法。我们向该方法传递一个参数,然后我们总是将块附加到方法调用的末尾:

self.card :contacts do |c|
  // a chunk of valid ruby code    
end

好的,我们现在需要将一段代码传递给一个方法。但是我们如何使用这个代码块呢?其中一种选择是将这个代码块转换成一个对象。Ruby提供了三种将代码块转换成对象的方式。

# lambda
> l = lambda { |a| a + 1 }
> l.call(1)
=> 2 

# Proc.new
> l2= Proc.new { |a| a + 1 }
> l2.call(1)
=> 2 

# & as the last method argument with a local variable name
def add(&block)
end

在上述方法中,& 将传递给方法的代码块转换为一个对象,并将该对象存储在本地变量 block 中。实际上,我们可以展示它具有与 lambda 和 Proc.new 相同的行为:

def add(&block)
  block
end

l3 = add { |a| a + 1 }
l3.call(1)
=> 2

这很重要。当你将一个块传递给一个方法并使用&进行转换时,所创建的对象会使用Proc.new来进行转换。

请注意,我避免使用"proc"作为选项。因为在Ruby 1.8中,它与lambda相同,在Ruby 1.9中,它与Proc.new相同,在所有Ruby版本中都应该避免使用。

那么你可能会问lambda和Proc.new之间有什么区别?

首先,在参数传递方面,lambda的行为类似于方法调用。如果您传递了错误数量的参数,它将引发异常。相反,Proc.new的行为类似于并行赋值。所有未使用的参数都会转换为nil:

> l = lambda {|a,b| puts "#{a} + #{b}" }
 => #<Proc:0x007fbffcb47e40@(irb):19 (lambda)> 
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)

> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21> 
> l2.call(1)
1 + 

其次,lambda和Proc.new在处理return关键字时有所不同。当你在Proc.new内部使用return时,它实际上是从封闭的方法(即周围的上下文)返回。当你从lambda块中返回时,它只是从块中返回,而不是从封闭的方法返回。基本上,它退出了对块的调用,并继续执行封闭方法的其余部分。

> def add(a,b)
  l = Proc.new { return a + b}
  l.call
  puts "now exiting method"
end
> add(1,1)
=> 2  # NOTICE it never prints the message "now exiting method"

> def add(a,b)
  l = lambda { return a + b }
  l.call
  puts "now exiting method"
end
> add(1,1)
=> now exiting method  # NOTICE this time it prints the message "now exiting method"

那么为什么会有这种行为差异呢?原因在于使用 Proc.new,我们可以在封闭方法的上下文中使用迭代器并得出逻辑结论。看下面这个例子:

> def print(max)
  [1,2,3,4,5].each do |val|
    puts val
    return if val > max
  end
end
> print(3)
1
2
3
4

我们期望当我们在迭代器内部调用return时,它将返回到封闭方法。请记住,传递给迭代器的块使用Proc.new转换为对象,这就是为什么当我们使用return时,它会退出封闭方法的原因。
您可以将lambda视为匿名方法,它们将单个代码块隔离到一个可以像方法一样处理的对象中。最终,将lambda视为行为类似于匿名方法,而Proc.new则行为类似于内联代码。

1

一篇关于 Ruby 指南的有用文章:块、Proc 和 Lambda

Proc 从当前方法返回,而 Lambda 从 Lambda 本身返回。

Proc 不关心正确数量的参数,而 Lambda 会引发异常。


-3

Proc和Lambda之间的区别在于,Proc只是一个代码副本,并按顺序替换参数,而Lambda则像其他语言中的函数一样。(返回行为,参数检查)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接