В Ruby 1.8 есть тонкие различия между proc / lambda, с одной стороны, и Proc.newс другой.
- Каковы эти различия?
- Можете ли вы дать рекомендации, как решить, какой выбрать?
- В Ruby 1.9 proc и lambda разные. В чем дело?
В Ruby 1.8 есть тонкие различия между proc / lambda, с одной стороны, и Proc.newс другой.
Ответы:
Другое важное, но тонкое различие между процессами, созданными с помощью, lambdaи процессами, созданными с помощью, Proc.newзаключается в том, как они обрабатывают returnоператор:
lambda-created proc, returnоператор возвращается только из самого procProc.new-created proc, returnоператор немного более удивителен: он возвращает контроль не только от proc, но и от метода, включающего 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созданный proc returnделает то же самое. Вы увидите один из тех случаев, когда Ruby нарушает хваленый принцип наименьшего сюрприза:
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"
Благодаря этому удивительному поведению (а также меньшему количеству печати) я предпочитаю использовать lambdaover Proc.newпри создании процедур.
procметод. Это просто сокращение для Proc.new?
procэквивалентноProc.new
procдействует как lambdaи не как в Proc.newотношении операторов возврата. Это означает, что рубиновый документ является неточным.
procдействует как lambdaв 1.8, но действует как Proc.newв 1.9. Смотрите ответ Питера Вагенета.
lambdaэто анонимный метод. Поскольку это метод, он возвращает значение, и вызвавший его метод может делать с ним все, что захочет, в том числе игнорировать его и возвращать другое значение. А Procэто как вставка во фрагмент кода. Это не действует как метод. Поэтому, когда происходит возврат в пределах Proc, это только часть кода метода, который его вызвал.
Для дальнейшего уточнения:
Джои говорит, что обратное поведение Proc.newудивительно. Однако, если учесть, что Proc.new ведет себя как блок, это неудивительно, поскольку именно так ведут себя блоки. ламбы, с другой стороны, ведут себя больше как методы.
Это фактически объясняет, почему Procs гибки, когда дело доходит до arity (количество аргументов), тогда как лямбды - нет. Блоки не требуют предоставления всех своих аргументов, а методы - (если не указано значение по умолчанию). Хотя предоставление аргумента лямбда по умолчанию не является опцией в Ruby 1.8, теперь он поддерживается в Ruby 1.9 с альтернативным синтаксисом лямбды (как отмечено в webmat):
concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1) # => "12"
И Мишель де Маре (ОП) не прав насчет проков и лямбда, которые ведут себя одинаково с 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.
Для получения дополнительной информации я настоятельно рекомендую язык программирования Ruby О'Рейли, который является источником большей части этой информации.
breakиз Procs повышается LocalJumpError, в то время как breakиз лямбд ведут себя так же , как return( то есть , return nil).
Я нашел эту страницу, которая показывает, какая разница между Proc.newи lambdaесть. Согласно странице, единственное отличие состоит в том, что лямбда строго определяет количество аргументов, которые она принимает, тогда как Proc.newпреобразует отсутствующие аргументы в nil. Вот пример сеанса IRB, иллюстрирующий разницу:
irb (основной): 001: 0> l = лямбда {| x, y | х + у}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (основной): 002: 0> p = Proc.new {| x, y | х + у}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (основной): 003: 0> l.call "привет", "мир"
=> "helloworld"
irb (main): 004: 0> p.call "привет", "мир"
=> "helloworld"
irb (основной): 005: 0> l.call "привет"
ArgumentError: неверное количество аргументов (1 для 2)
из (irb): 1
из (irb): 5: в "вызове"
из (irb): 5
от: 0
irb (основной): 006: 0> p.call "привет"
Ошибка типа: невозможно преобразовать ноль в строку
из (irb): 2: в `+ '
из (irb): 2
из (irb): 6: в "вызове"
из (irb): 6
от: 0
На этой странице также рекомендуется использовать лямбду, если вы не хотите, чтобы поведение было устойчивым к ошибкам. Я согласен с этим мнением. Использование лямбды кажется немного более кратким, и с таким незначительным отличием это кажется лучшим выбором в средней ситуации.
Что касается Ruby 1.9, извините, я еще не изучал 1.9, но я не думаю, что они сильно это изменят (хотя, не поверьте мне на слово, кажется, вы слышали о некоторых изменениях, поэтому Я наверное там не прав).
Proc старше, но семантика возврата очень противоречит мне (по крайней мере, когда я изучал язык), потому что:
Лямбда функционально безопаснее и легче рассуждать - я всегда использую ее вместо proc.
Я не могу сказать много о тонких различиях. Однако я могу отметить, что в Ruby 1.9 теперь разрешены необязательные параметры для лямбд и блоков.
Вот новый синтаксис для стабильных лямбд под 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, ознакомьтесь с этой статьей (бесстыдная самореклама).
Короткий ответ: важно то, что returnделает: лямбда возвращается из себя, а proc возвращает из себя и функцию, которая ее вызывала.
Менее понятно, почему вы хотите использовать каждый из них. Лямбда - это то, что мы ожидаем, что вещи должны делать в смысле функционального программирования. Это в основном анонимный метод с текущей областью, автоматически связанной. Из двух, лямбда - это та, которую вы, вероятно, должны использовать.
Proc, с другой стороны, действительно полезен для реализации самого языка. Например, вы можете реализовать операторы if или for для них. Любое возвращение, найденное в процедуре, будет возвращено из вызвавшего его метода, а не просто из оператора «if». Вот как работают языки, как работают операторы «если», так что я предполагаю, что Руби использует это под прикрытием, и они просто разоблачили это, потому что это казалось мощным.
Это действительно понадобится вам, если вы создаете новые языковые конструкции, такие как циклы, конструкции if-else и т. Д.
Хороший способ убедиться в том, что лямбда-выражения выполняются в собственной области видимости (как если бы это был вызов метода), в то время как Procs можно рассматривать как выполняемый inline с вызывающим методом, по крайней мере, это хороший способ решить, какой из них использовать в каждом случае.
Я не заметил никаких комментариев по поводу третьего метода в квесте, «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вернул лямбду в 1.8; теперь исправлено возвращение proc в 1.9 - однако это серьезное изменение; следовательно, не рекомендуется использовать больше
Замыкания в Ruby - это хороший обзор того, как блоки, лямбда и proc работают в Ruby с Ruby.
Лямбда работает как положено, как и на других языках.
Проводной 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, которое заключается в их обработке (неправильных) аргументов. Лямбда жалуется на это, 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создается процесс, передавая блок. Я считаю, что lambda {...}это синтаксический анализ, а не вызов метода, который передает блок. returnИзнутри блок, присоединенный к вызову метода, вернется из метода, а не из блока, и Proc.newпримером этого является случай.
(Это 1,8. Я не знаю, как это переводится в 1,9.)
Я немного опоздал на это, но есть одна замечательная, но малоизвестная вещь, о которой 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!
&blockаргумента в def, но без необходимости делать это в списке def arg.
Стоит подчеркнуть, что returnв процедуре возвращается лексически заключенный метод, т. Е. Метод, в котором он был создан , а не метод, вызвавший процедуру. Это является следствием свойства замыкания процедур. Поэтому следующий код ничего не выводит:
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это нормально в блоке, который выполняется в его лексическом контексте, но гораздо менее интуитивно понятен при использовании в процедуре, которая выполняется в другом контексте.
Разница в поведении с returnIMHO является наиболее важной разницей между 2. Я также предпочитаю лямбду, потому что она меньше печатает, чем Proc.new :-)
proc {}. Я не уверен, когда это вступило в силу, но это (немного) проще, чем вводить Proc.new.