Когда использовать лямбда, а когда - Proc.new?

В Ruby 1.8 есть тонкие различия между proc / lambda, с одной стороны, и Proc.new с другой.

  • Что это за различия?
  • Можете ли вы дать рекомендации, как решить, какой из них выбрать?
  • В Ruby 1.9 процедура и лямбда разные. В чем дело?

Ответов (14)

Решение

Еще одно важное, но тонкое различие между процедурами, созданными с помощью, lambda и процедурами, созданными с помощью, Proc.new заключается в том, как они обрабатывают return оператор:

  • В lambdaсозданной процедуре returnинструкция возвращается только из самой процедуры.
  • В Proc.new-created proc этот returnоператор немного более удивителен: он возвращает управление не только из процесса, но и из метода, содержащего процесс!

Вот lambda -созданный процесс return в действии. Он ведет себя так, как вы, вероятно, ожидаете:

def whowouldwin

  mylambda = lambda {return "Freddy"}
  mylambda.call

  # mylambda gets called and returns "Freddy", and execution
  # continues on the next line

  return "Jason"

end


whowouldwin
#=> "Jason"

Теперь то же самое делает Proc.new -созданный return процесс. Вы вот-вот увидите один из тех случаев, когда Руби нарушает хваленый принцип наименьшего сюрприза:

def whowouldwin2

  myproc = Proc.new {return "Freddy"}
  myproc.call

  # myproc gets called and returns "Freddy", 
  # but also returns control from whowhouldwin2!
  # The line below *never* gets executed.

  return "Jason"

end


whowouldwin2         
#=> "Freddy"

Благодаря этому удивительному поведению (а также меньшему количеству набора текста) я предпочитаю использовать lambda over Proc.new при создании процедур.

Разница в поведении с return IMHO - это самая важная разница между 2. Я также предпочитаю лямбда, потому что она меньше печатает, чем Proc.new :-)

лямбда работает должным образом, как и на других языках.

Проводное Proc.new - это удивительно и сбивает с толку.

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

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

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

И есть еще одно различие между лямбда-выражениями и Proc.new - обработка (неправильных) аргументов. lambda жалуется на это, Proc.new игнорируя лишние аргументы или рассматривая отсутствие аргументов как ноль.

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:[email protected](irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:[email protected](irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):21:in `block in irb_binding'
        from (irb):25:in `call'
        from (irb):25
        from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
        from (irb):47:in `block in irb_binding'
        from (irb):49:in `call'
        from (irb):49
        from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"

Кстати, proc в Ruby 1.8 создается лямбда, а в Ruby 1.9+ ведет себя как Proc.new, что действительно сбивает с толку.

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

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Несмотря на то, что процедура выполняется внутри foobar, она была создана, foo и поэтому return выходит foo, а не просто foobar . Как писал выше Чарльз Колдуэлл, в нем есть чувство GOTO. На мой взгляд, return это нормально в блоке, который выполняется в его лексическом контексте, но гораздо менее интуитивно понятен при использовании в процессе, который выполняется в другом контексте.

Я нашел эту страницу, которая показывает, в чем разница между Proc.new и lambda . Согласно странице, единственное отличие состоит в том, что лямбда строго определяет количество аргументов, которые она принимает, в то время как Proc.new преобразует отсутствующие аргументы в nil . Вот пример сеанса IRB, иллюстрирующий разницу:

irb (main): 001: 0> l = лямбда {| x, y | х + у}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (main): 002: 0> p = Proc.new {| x, y | х + у}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (main): 003: 0> l.call "привет", "мир"
=> "helloworld"
irb (main): 004: 0> p.call "привет", "мир"
=> "helloworld"
irb (main): 005: 0> l.call "привет"
ArgumentError: неправильное количество аргументов (1 вместо 2)
    от (irb): 1
    from (irb): 5: в `call '
    от (irb): 5
    из: 0
irb (main): 006: 0> p.call "привет"
TypeError: невозможно преобразовать nil в String
    from (irb): 2: в `+ '
    от (irb): 2
    from (irb): 6: в `call '
    от (irb): 6
    из: 0

На странице также рекомендуется использовать лямбда, если вы специально не хотите терпимого к ошибкам поведения. Я согласен с этим мнением. Использование лямбды кажется немного более кратким, и с такой незначительной разницей, это кажется лучшим выбором в средней ситуации.

Что касается Ruby 1.9, извините, я еще не изучал 1.9, но я не думаю, что они все это изменит (хотя не верьте мне на слово, похоже, вы слышали о некоторых изменениях, поэтому Я, наверное, ошибаюсь в этом).

Замыкания в Ruby - это хороший обзор того, как блоки, лямбда-выражения и процедуры работают в Ruby с Ruby.

Чтобы уточнить ответ Аккордеониста:

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

(Это 1.8. Я не знаю, как это переводится в 1.9.)

Proc старше, но семантика return мне очень противоречит интуиции (по крайней мере, когда я изучал язык), потому что:

  1. Если вы используете proc, вы, скорее всего, используете какую-то функциональную парадигму.
  2. Proc может возвращаться за пределы области действия (см. Предыдущие ответы), что в основном является goto и очень нефункционально по своей природе.

Lambda функционально безопаснее, и о ней легче рассуждать - я всегда использую ее вместо proc.

Я не могу много сказать о тонких различиях. Тем не менее, я могу отметить, что Ruby 1.9 теперь допускает необязательные параметры для лямбда-выражений и блоков.

Вот новый синтаксис stabby lambdas в версии 1.9:

stabby = ->(msg='inside the stabby lambda') { puts msg }

В Ruby 1.8 такого синтаксиса не было. Также традиционный способ объявления блоков / лямбда-выражений не поддерживает необязательные аргументы:

# under 1.8
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }

Однако Ruby 1.9 поддерживает необязательные аргументы даже со старым синтаксисом:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:[email protected](irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

Если вы хотите собрать Ruby1.9 для Leopard или Linux, прочтите эту статью (бессовестная самореклама).

Хороший способ увидеть, что лямбды выполняются в своей собственной области (как если бы это был вызов метода), в то время как Procs можно рассматривать как выполняемые в строке с вызывающим методом, по крайней мере, это хороший способ решить, какой из них использовать. в каждом случае.

Я не заметил никаких комментариев к третьему методу в квестоне, "proc", который устарел, но обрабатывается по-другому в 1.8 и 1.9.

Вот довольно подробный пример, который позволяет легко увидеть различия между тремя похожими вызовами:

def meth1
  puts "method start"

  pr = lambda { return }
  pr.call

  puts "method end"  
end

def meth2
  puts "method start"

  pr = Proc.new { return }
  pr.call

  puts "method end"  
end

def meth3
  puts "method start"

  pr = proc { return }
  pr.call

  puts "method end"  
end

puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3

Чтобы дать дополнительные пояснения:

Джои говорит, что ответное поведение Proc.new удивительно. Однако, если учесть, что Proc.new ведет себя как блок, это неудивительно, поскольку именно так ведут себя блоки. с другой стороны, ламбы больше похожи на методы.

Это фактически объясняет, почему Procs гибки, когда дело доходит до арности (количества аргументов), а лямбды - нет. Блоки не требуют предоставления всех своих аргументов, но методы требуют (если не указано значение по умолчанию). Хотя предоставление лямбда-аргумента по умолчанию не является опцией в Ruby 1.8, теперь оно поддерживается в Ruby 1.9 с альтернативным синтаксисом лямбда (как отмечает webmat):

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

И Мишель де Маре (OP) неверен в том, что Procs и лямбда ведут себя одинаково с arity в Ruby 1.9. Я проверил, что они по-прежнему поддерживают поведение с версии 1.8, как указано выше.

break операторы на самом деле не имеют особого смысла ни в Procs, ни в лямбдах. В Procs перерыв вернет вас из Proc.new, который уже был завершен. И нет никакого смысла отказываться от лямбды, поскольку это, по сути, метод, и вы никогда не откажетесь от верхнего уровня метода.

next, redo И raise ведут себя одинаково в обоих Procs и лямбды. В то время как retry это не допускается, и вызовет исключение.

И, наконец, этот proc метод никогда не следует использовать, поскольку он непоследователен и имеет неожиданное поведение. В Ruby 1.8 он фактически возвращает лямбду! В Ruby 1.9 это было исправлено, и он возвращает Proc. Если вы хотите создать Proc, придерживайтесь Proc.new .

Для получения дополнительной информации я настоятельно рекомендую O'Reilly's The Ruby Programming Language, который является моим источником большей части этой информации.

Краткий ответ: важно то, что return делает: лямбда возвращается из себя, а процедура возвращает из себя И функции, которая ее вызвала.

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

Proc, с другой стороны, действительно полезен для реализации самого языка. Например, с ними можно реализовать операторы if или циклы for. Любой возврат, найденный в процедуре, будет возвращен из метода, который его вызвал, а не только из оператора «if». Так работают языки, как работают операторы «если», поэтому я предполагаю, что Ruby использует это под прикрытием, и они просто раскрыли его, потому что это казалось мощным.

Вам это действительно понадобится, только если вы создаете новые языковые конструкции, такие как циклы, конструкции if-else и т. Д.

Я немного опоздал с этим, но есть одна замечательная, но малоизвестная вещь, о которой Proc.new вообще не упоминается в комментариях. Согласно документации :

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

Тем не менее, Proc.new позволяет объединить методы получения результатов:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!