В чем разница между proc и лямбда в Ruby?

и когда вы будете использовать один, а не другой?

7 ответов


большая разница в том как они обрабатывают аргументы. Создание proc с помощью proc {} и Proc.new {} эквивалентны. Однако, используя lambda {} дает вам proc, который проверяет количество переданных ему аргументов. От ri Kernel#lambda:

эквивалентно Proc.новый, кроме результирующих объектов 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)

кроме того, как указывает Кен, используя return внутри лямбда возвращает значение этой лямбды, но с помощью return в proc возвращается из заключительного блока.

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 etc. - эти контрольные слова. Предположим, у вас есть оператор 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

финал puts в методе, никогда не был выполнен, так как, когда мы назвали наш proc,return внутри это выбило нас из колеи. Если, однако, мы преобразуем наш proc в лямбду, мы получаем следующее:

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

возврат внутри лямбды только выбрасывает нас из самой лямбды, и заключительный метод продолжает выполняться. То, как ключевые слова потока управления обрабатываются в procs и lambdas, является основным различием между ними


Это несколько тонкий. Они оба метода, которые создают замыкания, и оба возвращают объекты Proc. На самом деле есть и третий способ ... --0-->. Разница в том, как они ведут себя, и особенности зависят от того, используете ли вы Ruby 1.8 или 1.9 (на самом деле, есть еще один способ создать их в Ruby 1.9). В общем случае, разница-это не то, о чем вам нужно беспокоиться. Только когда вы беспокоитесь о строгости, это имеет значение. когда использовать лямбда, когда использовать Proc.новый? охватывает различия довольно хорошо.


вообще говоря, lambdas более интуитивны, чем procs, потому что они больше похоже на методы. Они довольно строги к арити, и они просто выход при вызове return . По этой причине многие Рубинисты используют лямбды в качестве первый выбор, если им не нужны специфические особенности procs.

Procs: объекты класса Proc . Как и блоки, они оцениваются в объеме где они определены. лямбда: также объекты класса Proc но немного отличается от обычных процедур. Они являются закрытиями, такими как блоки и процессы, и как таковые они оцениваются в области, в которой они определены.

Создание Proc

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

создание лямбда

b = lambda { |x| x 2 }


вот еще один способ понять это.

блок-это фрагмент кода, присоединенный к вызову вызова метода на объекте. В приведенном ниже примере self является экземпляром анонимного класса, наследуемого от ActionView:: Base в Rails framework (который сам включает в себя множество вспомогательных модулей). карта-это метод, который мы призываем к себе. Мы передаем аргумент методу, а затем всегда прикрепляем блок к концу вызова метода:

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

Ok, таким образом, мы передаем фрагмент кода методу. Но как мы используем этот блок? Один из вариантов-преобразовать фрагмент кода в объект. 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

в приведенном выше методе & преобразует переданный методу блок в объект и сохраняет этот объект в блоке локальной переменной. Фактически, мы можем показать, что он имеет такое же поведение, как лямбда и Proc.new:

def add(&block)
  block
end

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

это важно. Когда вы передаете блок методу и преобразуете его с помощью&, объект, который он создает, использует Proc.новое для преобразования.

обратите внимание, что я избегал использования "proc" в качестве опции. Потому что это Руби 1.8, это так же, как и лямбда в Ruby 1.9, это же Тез.Докл.новых и во всех версиях Ruby этого следует избегать.

Итак, вы спрашиваете, в чем разница между лямбда и Proc.новый?

во-первых, с точки зрения передачи параметров, лямбда ведет себя, как способ вызов. Это вызовет исключение, если вы передадите неправильное количество аргументов. В отличие от этого прок.новый ведет себя как параллельное назначение. Все неиспользуемые аргументы преобразуются в 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 + 

во-вторых, лямбда и Proc.new обрабатывает ключевое слово return по-разному. Когда вы делаете возвращение внутри Proc.new, он фактически возвращается из метода enclosing, то есть из окружающего контекста. Когда вы возвращаетесь из лямбда-блока, он просто возвращается из блока, а не из метода enclosing. В основном, он выходит из вызова блока и продолжает выполнение с остальной частью метода enclosing.

> 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.новое, мы можем использовать итераторы в контексте методов вложения и делать логические выводы. Посмотрите на этот пример:

> 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 внутри итератора он вернется из метода enclosing. Помните, что блоки, переданные итераторам, преобразуются в объектов с помощью Тез.Докл.новый, и именно поэтому, когда мы используем return, он выйдет из метода enclosing.

вы можете думать о lambdas как анонимных методах, они изолируют отдельные блоки кода в объект, который можно рассматривать как метод. В конечном счете, думаю лямбда, как ведет себя, как способ anomyous и Тез.Докл.новое поведение как встроенный код.


есть только два основных отличия.

  • во-первых, a lambda проверяет количество переданных аргументов, в то время как proc нет. Это означает, что lambda выдаст ошибку, если вы передадите ей неправильное количество аргументов, тогда как proc проигнорирует неожиданные аргументы и назначит nil для всех, кто отсутствует.
  • во-вторых, когда lambda возвращает, он передает управление обратно вызывающему методу; когда proc возвращает, это так немедленно, не возвращаясь к методу вызова.

чтобы увидеть, как это работает, взгляните на приведенный ниже код. Наш первый метод вызывает a 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, однако, возвращается в метод после вызова, поэтому метод возвращает последний код оценивает: "Железный человек победит!"


различия между proc и лямбда заключается в том, что proc-это просто копия кода с замененными аргументами, в то время как лямбда-это функция, как и на других языках. (поведение возврата, проверка аргументов)