Ruby 中的 proc 和 lambda 有啥区别?

Posted

技术标签:

【中文标题】Ruby 中的 proc 和 lambda 有啥区别?【英文标题】:What's the difference between a proc and a lambda in Ruby?Ruby 中的 proc 和 lambda 有什么区别? 【发布时间】:2010-12-16 22:30:42 【问题描述】:

你什么时候会使用其中一个而不是另一个?

【问题讨论】:

除了 jtbandes 的回答之外,return 语句在 proclambda 中返回的内容也有所不同。 这是一个很好的博客awaxman11.github.io/blog/2013/08/05/… 这里有更详细的答案:***.com/questions/626/… 【参考方案1】:

一个区别在于它们处理参数的方式。使用 proc Proc.new 创建 proc 是等效的。但是,使用lambda 会为您提供一个检查传递给它的参数数量的过程。来自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

【讨论】:

准确地说 lambda 非常像方法(检查参数并返回将从它们返回),而 procs 非常像块(不检查参数,返回将从包含返回方法还是 lambda)? 我去过天知道有多少网站和文章到目前为止,似乎没有人谈论 Procs 与方法与 lambdas 的实用性。每个解释都只是提供了关于返回值等如何不同的令人毛骨悚然的细节,但没有说明为什么重要。现在我不得不得出结论,这是 Ruby 中的设计混乱。【参考方案2】:

procs 和 lambdas 之间的真正区别与控制流关键字有关。我说的是returnraisebreakredoretry 等等——那些控制词。假设您在 proc 中有一个 return 语句。当你调用你的 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

方法中的最后一个puts 从未被执行,因为当我们调用我们的proc 时,其中的return 将我们从方法中丢弃。但是,如果我们将 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 中的 return 只会将我们从 lambda 本身中转储出来,并且封闭的方法会继续执行。 procs和lambdas中处理控制流关键字的方式是它们之间的主要区别

【讨论】:

【参考方案3】:

只有两个主要区别。

首先,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_ironman_proc 方法。

然而,我们的lambda 在被调用后会返回到方法中,因此该方法返回它评估的最后一个代码:“钢铁侠会赢!”

【讨论】:

【参考方案4】:

# 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           

Procs 和 Lambda 之间的区别

在讨论 procs 和 lambdas 之间的区别之前,重要的是要提到它们都是 Proc 对象。

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

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

但是,lambdas 是 procs 的另一种“风格”。返回对象时会显示这种细微的差异。

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)

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

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。 Lambda 和 proc 对“return”关键字的处理方式不同

lambda 内部的“return”会触发 lambda 代码之外的代码

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

lambda_test                 # calling lambda_test prints 'Hello World'

proc 内部的'return' 触发 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。

原帖here

【讨论】:

【参考方案5】:

一般来说,lambdas 比 procs 更直观,因为它们是 更类似于方法。他们对 arity 非常严格,而且他们只是 调用 return 时退出。出于这个原因,许多 Ruby 爱好者使用 lambdas 作为 首选,除非他们需要 procs 的特定功能。

过程: Proc 类的对象。像块一样,它们在范围内进行评估 它们被定义的地方。 Lambdas: 也是 Proc 类的对象,但与常规 procs 略有不同。 它们是像块和过程一样的闭包,因此它们在 定义它们的范围。

创建过程

a = Proc.new  |x| x 2 

创建 lambda

b = lambda |x| x 2

【讨论】:

a = proc |x| x 2 a = Proc.new |x| x 2 相同【参考方案6】:

这是理解这一点的另一种方式。

块是附加到对对象方法调用的调用的一段代码。在下面的示例中,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

在上面的方法中, & 将传递给方法的块转换为一个对象,并将该对象存储在局部变量块中。事实上,我们可以证明它与 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 时,它将退出封闭方法。

您可以将 lambdas 视为匿名方法,它们将各个代码块隔离到一个可以像方法一样对待的对象中。最终,将 lambda 视为异常方法,而 Proc.new 视为内联代码。

【讨论】:

【参考方案7】:

关于 ruby​​ 指南的有用帖子:blocks, procs & lambdas

Procs 从当前方法返回,而 lambdas 从 lambda 本身返回。

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

【讨论】:

【参考方案8】:

proc 和 lambda 的区别在于 proc 只是一个代码的副本,参数依次替换,而 lambda 是一个函数,就像其他语言一样。 (返回行为,参数检查)

【讨论】:

以上是关于Ruby 中的 proc 和 lambda 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

方法和过程对象有啥区别?

ruby Ruby Proc,Lambda和Closure

Ruby:将proc转换为lambda?

Ruby - lambda 与 Proc.new [重复]

Java 中 给一个object 赋值属性, 既可以用构造函数的方式,也可以用setXXXX()的方式,而它们之间有啥区

为啥我应该使用 lambda/proc ?解释它的重要性 [重复]