Вот еще один способ понять это.
Блок - это фрагмент кода, прикрепленный к вызову метода объекта. В приведенном ниже примере self является экземпляром анонимного класса, унаследованного от ActionView :: Base в платформе Rails (который сам включает в себя множество вспомогательных модулей). карта - это метод, который мы называем собой. Мы передаем аргумент методу, а затем всегда присоединяем блок к концу вызова метода:
self.card :contacts do |c|
// a chunk of valid ruby code
end
Хорошо, мы передаем часть кода методу. Но как мы используем этот блок? Один из вариантов - преобразовать кусок кода в объект. 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.new для конвертации.
Обратите внимание, что я избежал использования "proc" в качестве опции. Это потому, что это Ruby 1.8, он такой же, как лямбда, а в Ruby 1.9 он такой же, как Proc.new, и во всех версиях Ruby его следует избегать.
Итак, вы спрашиваете, в чем разница между лямбдой и Proc.new?
Во-первых, с точки зрения передачи параметров, лямбда ведет себя как вызов метода. Это вызовет исключение, если вы передадите неверное количество аргументов. Proc.new, напротив, ведет себя как параллельное присваивание. Все неиспользованные аргументы преобразуются в 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 +
Во-вторых, lambda и Proc.new обрабатывают ключевое слово return по-разному. Когда вы делаете возврат внутри Proc.new, он фактически возвращается из метода включения, то есть из окружающего контекста. Когда вы возвращаетесь из лямбда-блока, он просто возвращается из блока, а не из-за метода включения. По сути, он выходит из вызова в блок и продолжает выполнение с остальной частью включающего метода.
> 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.new мы можем использовать итераторы внутри контекста вложенных методов и делать логические выводы. Посмотрите на этот пример:
> 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 внутри итератора, он вернется из включающего метода. Помните, что блоки, переданные итераторам, преобразуются в объекты с использованием Proc.new, и поэтому, когда мы используем return, он выходит из метода включения.
Вы можете думать о лямбдах как о анонимных методах, они изолируют отдельные блоки кода в объект, который можно рассматривать как метод. В конечном счете, представьте, что лямбда ведет себя как аномный метод, а Proc.new ведет себя как встроенный код.