В чем разница между proc и лямбда в Ruby?
и когда вы будете использовать один, а не другой?
7 ответов
большая разница в том как они обрабатывают аргументы. Создание proc с помощью proc {}
и Proc.new {}
эквивалентны. Однако, используя lambda {}
дает вам proc, который проверяет количество переданных ему аргументов. От ri Kernel#lambda
:
эквивалентно Proc.новый, кроме результирующих объектов 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
в proc возвращается из заключительного блока.
lambda { return :foo }.call # => :foo
return # => LocalJumpError: unexpected return
Proc.new { return :foo }.call # => LocalJumpError: unexpected return
поэтому для большинства быстрых применений они одинаковы, но если вы хотите автоматическую строгую проверку аргументов (что также может иногда помочь в отладке), или если вам нужно использовать return
оператор для возврата значения proc используйте lambda
.
реальная разница между procs и lambdas имеет все, что связано с ключевыми словами потока управления. Я говорю о return
, raise
, break
, redo
, retry
etc. - эти контрольные слова. Предположим, у вас есть оператор return в proc. Когда вы вызываете свой proc, он не только выгружает вас из него, но и возвращается из заключительного метода, например:
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
внутри это выбило нас из колеи. Если, однако, мы преобразуем наш proc в лямбду, мы получаем следующее:
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
возврат внутри лямбды только выбрасывает нас из самой лямбды, и заключительный метод продолжает выполняться. То, как ключевые слова потока управления обрабатываются в procs и lambdas, является основным различием между ними
Это несколько тонкий. Они оба метода, которые создают замыкания, и оба возвращают объекты Proc. На самом деле есть и третий способ ... --0-->. Разница в том, как они ведут себя, и особенности зависят от того, используете ли вы Ruby 1.8 или 1.9 (на самом деле, есть еще один способ создать их в Ruby 1.9). В общем случае, разница-это не то, о чем вам нужно беспокоиться. Только когда вы беспокоитесь о строгости, это имеет значение. когда использовать лямбда, когда использовать Proc.новый? охватывает различия довольно хорошо.
вообще говоря, lambdas более интуитивны, чем procs, потому что они больше похоже на методы. Они довольно строги к арити, и они просто выход при вызове return . По этой причине многие Рубинисты используют лямбды в качестве первый выбор, если им не нужны специфические особенности procs.
Procs: объекты класса Proc
. Как и блоки, они оцениваются в объеме
где они определены.
лямбда: также объекты класса Proc
но немного отличается от обычных процедур.
Они являются закрытиями, такими как блоки и процессы, и как таковые они оцениваются в
области, в которой они определены.
Создание Proc
a = Proc.new { |x| x 2 }
создание лямбда
b = lambda { |x| x 2
}
вот еще один способ понять это.
блок-это фрагмент кода, присоединенный к вызову вызова метода на объекте. В приведенном ниже примере self является экземпляром анонимного класса, наследуемого от ActionView:: Base в Rails framework (который сам включает в себя множество вспомогательных модулей). карта-это метод, который мы призываем к себе. Мы передаем аргумент методу, а затем всегда прикрепляем блок к концу вызова метода:
self.card :contacts do |c|
// a chunk of valid ruby code
end
Ok, таким образом, мы передаем фрагмент кода методу. Но как мы используем этот блок? Один из вариантов-преобразовать фрагмент кода в объект. 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.новое для преобразования.
обратите внимание, что я избегал использования "proc" в качестве опции. Потому что это Руби 1.8, это так же, как и лямбда в Ruby 1.9, это же Тез.Докл.новых и во всех версиях Ruby этого следует избегать.
Итак, вы спрашиваете, в чем разница между лямбда и Proc.новый?
во-первых, с точки зрения передачи параметров, лямбда ведет себя, как способ вызов. Это вызовет исключение, если вы передадите неправильное количество аргументов. В отличие от этого прок.новый ведет себя как параллельное назначение. Все неиспользуемые аргументы преобразуются в 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 +
во-вторых, лямбда и Proc.new обрабатывает ключевое слово return по-разному. Когда вы делаете возвращение внутри Proc.new, он фактически возвращается из метода enclosing, то есть из окружающего контекста. Когда вы возвращаетесь из лямбда-блока, он просто возвращается из блока, а не из метода enclosing. В основном, он выходит из вызова блока и продолжает выполнение с остальной частью метода enclosing.
> 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.новое, мы можем использовать итераторы в контексте методов вложения и делать логические выводы. Посмотрите на этот пример:
> 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 внутри итератора он вернется из метода enclosing. Помните, что блоки, переданные итераторам, преобразуются в объектов с помощью Тез.Докл.новый, и именно поэтому, когда мы используем return, он выйдет из метода enclosing.
вы можете думать о lambdas как анонимных методах, они изолируют отдельные блоки кода в объект, который можно рассматривать как метод. В конечном счете, думаю лямбда, как ведет себя, как способ anomyous и Тез.Докл.новое поведение как встроенный код.
есть только два основных отличия.
- во-первых, a
lambda
проверяет количество переданных аргументов, в то время какproc
нет. Это означает, чтоlambda
выдаст ошибку, если вы передадите ей неправильное количество аргументов, тогда какproc
проигнорирует неожиданные аргументы и назначитnil
для всех, кто отсутствует. - во-вторых, когда
lambda
возвращает, он передает управление обратно вызывающему методу; когдаproc
возвращает, это так немедленно, не возвращаясь к методу вызова.
чтобы увидеть, как это работает, взгляните на приведенный ниже код. Наш первый метод вызывает a 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
, однако, возвращается в метод после вызова, поэтому метод возвращает последний код оценивает: "Железный человек победит!"
различия между proc и лямбда заключается в том, что proc-это просто копия кода с замененными аргументами, в то время как лямбда-это функция, как и на других языках. (поведение возврата, проверка аргументов)