簡體   English   中英

Ruby 中的 proc 和 lambda 之間有什么區別?

[英]What's the difference between a proc and a lambda in Ruby?

你什么時候會使用一個而不是另一個?

一個區別在於它們處理參數的方式。 使用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

procs 和 lambdas 之間的真正區別與控制流關鍵字有關。 我說的是return , raise , break , redo , retry等等——這些控制詞。 假設您在 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 中處理控制流關鍵字的方式是它們之間的主要區別

只有兩個主要區別。

  • 首先, 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在被調用后會返回到該方法中,因此該方法會返回它評估的最后一個代碼:“鋼鐵俠會贏!”

# 過程示例

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 和 Lambdas 之間的差異

在我討論 procs 和 lambdas 之間的區別之前,重要的是要提到它們都是 Proc 對象。

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

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

但是,lambda 是 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)

相比之下,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'

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。

最初發布在這里

一般來說,lambdas 比 procs 更直觀,因為它們更類似於方法。 他們對 arity 非常嚴格,當你調用 return 時他們就會退出。 出於這個原因,許多 Ruby 專家使用 lambdas 作為首選,除非他們需要 procs 的特定功能。

Procs:Proc對象。 像塊一樣,它們在定義它們的范圍內進行評估。 Lambdas:也是Proc類的對象,但與常規Proc略有不同。 它們是像塊和過程這樣的閉包,因此它們在定義它們的范圍內進行評估。

創建過程

a = Proc.new { |x| x 2 }

創建 lambda

b = lambda { |x| x 2 b = lambda { |x| x 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

在上面的方法中, & 將傳遞給方法的塊轉換為對象並將該對象存儲在局部變量塊中。 事實上,我們可以證明它與 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 視為內聯代碼。

關於 ruby​​ 指南的有用帖子: blocks, procs & lambdas

Procs 從當前方法返回,而 lambdas 從 lambda 本身返回。

Procs 不關心參數的正確數量,而 lambdas 會引發異常。

proc 和 lambda 之間的區別在於 proc 只是一個代碼副本,依次替換了參數,而 lambda 是一個函數,就像在其他語言中一樣。 (返回行為,參數檢查)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM