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

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

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

person Michiel de Mare    schedule 03.08.2008    source источник
comment
См. Также: книгу Матца и Фланагана по языку программирования Ruby, в которой эта тема исчерпывающе раскрыта. proc ведет себя как семантика выхода блока, а лямбда - как семантика вызова метода. Также return, break, et. все ведут себя по-разному в процедурах и лямбдах   -  person Gishu    schedule 08.02.2010
comment
Также см. Подробный пост на Различия в потоке управления между Ruby Procs и Lambdas   -  person Akshay Rawat    schedule 31.03.2013
comment
вы приняли ответ, в котором только говорится, в чем разница между proc и lambda, а заголовок вашего вопроса - когда использовать эти вещи   -  person Shri    schedule 16.09.2016


Ответы (14)


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

  • В процессе, созданном lambda, оператор return возвращается только из самого процесса.
  • В процедуре, созданной Proc.new_, инструкция 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 вместо Proc.new при создании процедур.

person Joey deVilla    schedule 03.08.2008
comment
Еще есть метод proc. Это просто сокращение от Proc.new? - person panzi; 19.11.2010
comment
@panzi, да, proc эквивалентно Proc.new - person ma11hew28; 17.02.2011
comment
@mattdipasquale В моих тестах proc действует как lambda, а не как Proc.new в отношении операторов возврата. Это означает, что рубиновый документ неточен. - person Kelvin; 02.08.2011
comment
@mattdipasquale Извини, я был прав только наполовину. proc действует как lambda в 1.8, но действует как Proc.new в 1.9. См. Ответ Питера Вагенета. - person Kelvin; 02.08.2011
comment
Почему такое удивительное поведение? lambda - анонимный метод. Поскольку это метод, он возвращает значение, а вызвавший его метод может делать с ним все, что захочет, включая его игнорирование и возврат другого значения. Proc - это как вставка во фрагмент кода. Это не действует как метод. Поэтому, когда возврат происходит внутри Proc, это всего лишь часть кода метода, который его вызвал. - person Arcolye; 24.12.2012
comment
Как указал Арколай, процедуры в Ruby - это фрагменты кода, а не методы. Из robertsosinski.com/2008/12/ 21 /. Так что возвращение происходит в самом whowouldwin2. - person Pietro; 12.01.2013
comment
Я считаю, что существенное различие также в том, что Procs не выдает ошибок, когда вы указываете недостающие / дополнительные аргументы, а lambdas выдает ошибку wrong number of arguments - person bigpotato; 01.08.2013
comment
Ответ на этот счет неясен, и приведенный выше комментарий Аркойла просто неверен: return в Proc возвращается из контекста, который создал процесс, не из контекста, который вызвал процесс. Эта ошибка везде! - person ComDubh; 14.12.2018

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

Джои говорит, что поведение 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 Язык программирования Ruby, который является моим источником большей части этой информации.

person Peter Wagenet    schedule 04.10.2009
comment
Однако, если учесть, что Proc.new ведет себя как блок, это неудивительно, поскольку именно так ведут себя блоки. ‹- блок является частью объекта, а Proc.new создает объект. И лямбда, и Proc.new создают объект с классом Proc, почему diff? - person weakish; 29.10.2014
comment
Начиная с Ruby 2.5, break из Procs повышает LocalJumpError, тогда как break из лямбда-выражений ведет себя так же, как return (т.е., return nil). - person Masa Sakano; 16.10.2018

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

irb(main):001:0> l = lambda { |x, y| x + y }
=> #<Proc:0x00007fc605ec0748@(irb):1>
irb(main):002:0> p = Proc.new { |x, y| x + y }
=> #<Proc:0x00007fc605ea8698@(irb):2>
irb(main):003:0> l.call "hello", "world"
=> "helloworld"
irb(main):004:0> p.call "hello", "world"
=> "helloworld"
irb(main):005:0> l.call "hello"
ArgumentError: wrong number of arguments (1 for 2)
    from (irb):1
    from (irb):5:in `call'
    from (irb):5
    from :0
irb(main):006:0> p.call "hello"
TypeError: can't convert nil into String
    from (irb):2:in `+'
    from (irb):2
    from (irb):6:in `call'
    from (irb):6
    from :0

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

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

person Mike Stone    schedule 03.08.2008
comment
procs также возвращаются иначе, чем лямбды. - person Cam; 21.02.2013
comment
Proc.new преобразует отсутствующие аргументы в nil Proc.new также игнорирует лишние аргументы (конечно, лямбда сообщает об ошибке). - person weakish; 29.10.2014

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

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

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

person Community    schedule 10.09.2008

Я не могу много сказать о тонких различиях. Тем не менее, я могу указать, что 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:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

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

person webmat    schedule 19.11.2008
comment
Необязательные параметры в лямбдах были очень необходимы, я рад, что они добавили их в 1.9. Я предполагаю, что блоки также могут иметь необязательные параметры (в 1.9)? - person mpd; 08.12.2010
comment
вы не демонстрируете параметры по умолчанию в блоках, только лямбды - person iconoclast; 25.03.2013

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

person krusty.ar    schedule 09.12.2008

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

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

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

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

person Evan Moran    schedule 06.10.2011
comment
lambda возвращается из себя, а proc возвращается из себя, И функция, которая вызвала его, является явной ошибкой и очень распространенным недоразумением. Процесс - это закрытие, которое возвращается из метода, который его создал. Смотрите мой полный ответ в другом месте на странице. - person ComDubh; 15.11.2018

Я не заметил никаких комментариев к третьему методу в квестоне, "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
person Dave Rapin    schedule 25.06.2009
comment
Мац заявил, что он планировал отказаться от него, потому что было непонятно, что proc и Proc.new возвращают разные результаты. Однако в 1.9 они ведут себя так же (proc - это псевдоним для Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47 - person Dave Rapin; 30.01.2010
comment
@banister: proc вернул лямбду в 1.8; теперь исправлено возвращать процесс в 1.9 - однако это критическое изменение; поэтому больше не рекомендуется использовать - person Gishu; 08.02.2010
comment
Я думаю, что кирка где-то в сноске говорит, что прок эффективно лишен или что-то в этом роде. У меня нет точного номера страницы. - person dertoni; 04.06.2010

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

person Community    schedule 28.08.2008
comment
Я перестал читать это после того, как прочитал, что функция не может принимать несколько блоков - это нарушает принцип, согласно которому замыкания могут свободно передаваться как значения. Блоки - это не закрытие. Процедуры есть, и функция может принимать несколько процедур. - person ComDubh; 14.12.2018

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

Проводной 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 вставляет код во включающий метод, как и block. Но Proc.new создает объект, а блоки являются частью объекта.

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

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(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, что действительно сбивает с толку.

person weakish    schedule 29.10.2014

Чтобы подробнее рассказать об ответе Accordion Guy:

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

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

person Peeja    schedule 07.09.2008

Я немного опоздал с этим, но есть одна замечательная, но малоизвестная вещь о 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!
person Aleksei Matiushkin    schedule 28.04.2015
comment
Интересно, что он делает то же самое, что и объявление аргумента &block в def, но без необходимости делать это в списке аргументов def. - person jrochkind; 08.02.2016

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

person ComDubh    schedule 15.11.2018

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

person Orion Edwards    schedule 11.08.2008
comment
Для обновления: теперь процессы можно создавать с помощью proc {}. Я не уверен, когда это вступило в силу, но это (немного) проще, чем набирать Proc.new. - person aceofbassgreg; 17.08.2013