В чем разница между процедурой и лямбдой в Ruby? - PullRequest
166 голосов
/ 16 ноября 2009

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

Ответы [ 8 ]

249 голосов
/ 16 ноября 2009

Одно отличие заключается в том, как они обрабатывают аргументы. Создание процедуры с использованием proc {} и Proc.new {} эквивалентны. Однако использование 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)

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

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

Так что для большинства быстрых применений они одинаковы, но если вам нужна автоматическая строгая проверка аргументов (которая иногда может также помочь с отладкой), или если вам нужно использовать оператор return для возврата значения proc , используйте lambda.

69 голосов
/ 24 марта 2015

Реальная разница между процессами и лямбдами связана с ключевыми словами потока управления. Я говорю о return, raise, break, redo, retry и т. Д. - эти контрольные слова. Допустим, у вас есть инструкция возврата в процедуре. Когда вы вызываете свой процесс, он не только выводит вас из него, но и возвращает из метода вложения, например ::1006

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 внутри него выбрасывал нас из метода. Однако, если мы конвертируем наш процесс в лямбду, мы получим следующее:

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

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

5 голосов
/ 06 августа 2018

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

  • Сначала 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 возвращается в метод после вызова, поэтому метод возвращает последний код, который он оценивает: «Железный человек победит!»

2 голосов
/ 03 апреля 2019

# Примеры процессов

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

# Лямбда Примеры

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

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

Различия между процами и лямбдами

Прежде чем углубляться в различия между процессами и лямбдами, важно упомянуть, что они оба являются объектами Proc.

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

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

Однако лямбды - это другой «вкус» проков. Эта небольшая разница проявляется при возврате объектов.

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

1. Лямбды проверяют количество аргументов, а процы не

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.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

«Возврат» внутри лямбда-кода запускает код прямо за пределами лямбда-кода

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 для возврата значение процедуры, используйте лямбду.

Первоначально опубликовано здесь

1 голос
/ 28 мая 2018

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

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

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

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

.
def add(&block)
  block
end

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

Это ВАЖНО. Когда вы передаете блок методу и конвертируете его с помощью &, создаваемый им объект использует Proc.new для конвертации.

Обратите внимание, что я избежал использования "proc" в качестве опции. Это потому, что это Ruby 1.8, он такой же, как лямбда, а в Ruby 1.9 он такой же, как Proc.new, и во всех версиях Ruby его следует избегать.

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

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

> 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, он выходит из метода включения.

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

1 голос
/ 21 мая 2017

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

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

Создание процесса

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

Создание лямбды

b = lambda { |x| x 2}

0 голосов
/ 23 марта 2019

Полезный пост на рубиновых путеводителях: блоки, процы и лямбды

Процедуры возвращаются из текущего метода, а лямбды возвращаются из самой лямбды.

Procs не заботится о правильном количестве аргументов, в то время как лямбды вызовут исключение.

0 голосов
/ 04 ноября 2015

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...