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

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

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

См. Также: книгу Матца и Фланагана по языку программирования Ruby, в которой эта тема исчерпывающе раскрыта. proc ведет себя как семантика выхода блока, тогда как лямбда ведет себя как семантика вызова метода. Также return, break, et. все ведут себя по-разному в процедурах и лямбдах

Gishu 08.02.2010 16:03

Также смотрите подробный пост на Различия в потоке управления между Ruby Procs и Lambdas

Akshay Rawat 31.03.2013 17:13

вы приняли ответ, в котором только говорится, в чем разница между proc и lambda, а заголовок вашего вопроса - когда использовать эти вещи

Shri 16.09.2016 08:58
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
339
3
79 838
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

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

procs также возвращаются иначе, чем лямбды.

Cam 21.02.2013 00:10

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

weakish 29.10.2014 19:14
Ответ принят как подходящий

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

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

Вот созданный 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 при создании процедур.

Еще есть метод proc. Это просто сокращение от Proc.new?

panzi 19.11.2010 18:49

@panzi, да, proc эквивалентен Proc.new

ma11hew28 17.02.2011 22:13

@mattdipasquale В моих тестах proc действует как lambda, а не как Proc.new в отношении операторов возврата. Это означает, что рубиновый документ неточен.

Kelvin 02.08.2011 19:10

@mattdipasquale Извини, я был прав только наполовину. proc действует как lambda в 1.8, но действует как Proc.new в 1.9. См. Ответ Питера Вагенета.

Kelvin 02.08.2011 19:20

Почему такое «удивительное» поведение? lambda - анонимный метод. Поскольку это метод, он возвращает значение, а вызвавший его метод может делать с ним все, что захочет, в том числе игнорировать его и возвращать другое значение. Proc похож на вставку фрагмента кода. Это не действует как метод. Поэтому, когда возврат происходит внутри Proc, это всего лишь часть кода метода, который его вызвал.

Arcolye 24.12.2012 07:13

Как указал Арколай, «процессы в Ruby - это фрагменты кода, а не методы». От robertsosinski.com/2008/12/21/…. Так что возвращение происходит в самом whowouldwin2.

Pietro 12.01.2013 20:35

Я считаю, что основное различие заключается также в том, что Proc не вызывает ошибок, когда вы указываете отсутствующие / дополнительные аргументы, тогда как lambdas выдает ошибку wrong number of arguments.

bigpotato 01.08.2013 22:24

Ответ на этот счет неясен, и приведенный выше комментарий Аркойла просто неверен: return в Proc возвращается из контекста, который создал процесс, не контекст, который вызвал процесс. Эта ошибка везде!

ComDubh 14.12.2018 19:19

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

Для обновления: теперь процессы можно создавать с помощью proc {}. Я не уверен, когда это вступило в силу, но это (немного) проще, чем набирать Proc.new.

aceofbassgreg 17.08.2013 19:42

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

Я перестал читать это после того, как прочитал «функция не может принимать несколько блоков - нарушая принцип, согласно которому замыкания могут свободно передаваться как значения». Блоки - это не закрытие. Процедуры есть, и функция может принимать несколько процедур.

ComDubh 14.12.2018 19:10

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

Обратите внимание, что 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:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

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

Необязательные параметры в лямбдах были очень необходимы, я рад, что они добавили их в 1.9. Я предполагаю, что блоки также могут иметь необязательные параметры (в 1.9)?

mpd 08.12.2010 20:43

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

iconoclast 25.03.2013 02:43

Хороший способ увидеть, что лямбды выполняются в своей собственной области (как если бы это был вызов метода), в то время как 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 и Proc.new возвращают разные результаты. Однако в 1.9 они ведут себя так же (proc - это псевдоним для Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47

Dave Rapin 30.01.2010 16:18

@banister: proc вернул лямбду в версии 1.8; теперь исправлено возвращать процесс в 1.9 - однако это критическое изменение; поэтому больше не рекомендуется использовать

Gishu 08.02.2010 16:05

Я думаю, что кирка где-то в сноске говорит, что прок эффективно лишен или что-то в этом роде. У меня нет точного номера страницы.

dertoni 04.06.2010 13:20

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

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

"" "Однако, если учесть, что Proc.new ведет себя как блок, это не удивительно, поскольку именно так ведут себя блоки." ""

weakish 29.10.2014 19:13

Начиная с Ruby 2.5, break из Procs поднимает LocalJumpError, тогда как break из лямбда-выражений ведет себя так же, как return (т.е., return nil).

Masa Sakano 16.10.2018 23:35

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

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

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

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

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

ComDubh 15.11.2018 13:11

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

Проводной 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: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, что действительно сбивает с толку.

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

Proc::new may be called without a block only within a method with an attached block, in which case that block is converted to the Proc object.

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

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

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

Интересно, что он делает то же самое, что и объявление аргумента &block в def, но без необходимости делать это в списке def arg.

jrochkind 08.02.2016 08:02

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

Другие вопросы по теме