Возникли проблемы с пониманием разницы между рубиновыми блоками, процедурами и ламами. Что такое блоки? В чем разница между procs и lambdas? Давайте разберемся с этим.
БЛОКИ
Блок - это набор кода, заключенный в оператор do / end или в фигурные скобки {}. Это фрагменты кода, которые вы можете взять и передать другому методу в качестве входных данных, или фрагмент кода, который вы связываете с вызовом метода. Если вы использовали each
before для перебора Enumerable, значит, вы использовали блоки.
Определение блока
def block_method puts "we are in the method" end block_method { puts "The block is called"}
Здесь мы определили метод block_method. Ниже приведен вызов метода, после которого мы передаем блок.
Вы можете угадать результат?
Да, вы правильно угадали.
> we are in the method
Вывод только определенного метода. Это потому, что мы никоим образом не вызывали блок. Давайте посмотрим, как мы вызываем блок в следующем разделе.
Получение блока
def my_method puts "we are in the method" yield puts "we are back in the method" end my_method { puts "The block is called"}
Что на самом деле происходит в приведенном выше коде?
Сначала мы создали метод my_method. Затем в следующей строке печатаем строку we are in the method
. Обратите внимание, что в следующей строке мы использовали ключевое слово yield
, которое найдет и вызовет блок, с которым был вызван метод.
Здесь происходит следующее: yield перейдет к вызову метода и выполнит блок, после чего управление вернется к методу, чтобы возобновить выполнение тела метода. В нашем случае мы вызвали метод my_method, а затем передали блок с помощью {}. Yield выполнит код блока, который был передан после вызова my_method, после чего выполнение тела метода продолжится.
Передача параметров в блок
Что делать, если вы хотите передать параметры yield. Подумайте о том, как вы передаете аргументы таким методам, как каждый, когда вы даете ему блок.
[1,2,3].each {|x| puts x*2 }
В этом случае каждый метод принимает блок с аргументом. Как насчет того, чтобы сделать это с нашим определенным блоком и посмотреть, как yield
принимает аргументы.
def my_block yield 2 yield 3 end my_block {|parameter| puts "parameter is: #{parameter}" }
Здесь yield вызовет блок, переданный с вызовом метода. В нашем случае дайте блоку аргумент, поскольку блок принимает параметр. В первом раунде он вызовет блок с параметром, равным 2. Управление возвращается к методу, а затем снова вызывает блок, на этот раз с параметром, равным 3.
> parameter is: 2 > parameter is: 3
Интересные факты о блоках
- Знаете ли вы, что можно вызывать каждый метод перечислимого объекта и не передавать ему блок? Он вернет вам перечислимый объект https://ruby-doc.org/core-2.5.0/Enumerator.html#method-i-each
- Внутри блоков, которые нельзя вернуть явно, блоки возвращают последнюю строку выполнения
- При вызове каждого метода последняя строка обычно игнорируется, и каждый метод возвращает исходный перечисляемый объект
- При вызове карты последняя строка добавляется в массив, который будет возвращен
ПРОЦЕДУРЫ
Так что, если вы хотите передать своей функции два блока. Как вы можете сохранить свой блок в переменной?
Ruby вводит процедуры, чтобы мы могли передавать блоки. Объекты Proc - это блоки кода, которые были привязаны к набору локальных переменных. После привязки код может вызываться в разных контекстах и по-прежнему обращаться к этим переменным.
Определение процедур
Вы можете вызвать new в классе Proc, чтобы создать proc. Вы можете использовать объект ядра proc
. Метод Proc - это просто псевдоним для Proc.new. Это может быть присвоено переменной.
factor = Proc.new {|n| print n*2 } or factor = proc {|n| print n*2} //using the proc value [3,2,1].each(&factor) >642
Мы ставим перед аргументом &
, чтобы ruby знал, что это процедура, а не переменная.
Определение метода, который принимает процесс / блок
def my_each(&block ) self.length.times do |i| # and now we can call our new Proc like normal block.call( self[i] ) end end [1,2,3].my_each { |i| puts i*2 }
В нашем определении &
преобразует блок в процесс, поэтому мы рассматриваем блок как процесс внутри нашего метода.
Мы больше не используем yield, поскольку методу можно передать более одного процесса, мы должны сказать, какой из них мы вызываем.
В наших методах есть разные способы вызова процедур. Использование call
, ()
или [].
ЛЯМБДА
Может быть определен с использованием метода лямбда или может быть определен как укороченный лямбда
lamb = lambda {|n| puts 'I am a lambda' } lamb = -> (n) { puts 'I am a stuby lambda' }
Разница между процедурами и лямбдами
- Procs не заботятся о правильном количестве аргументов, а лямбда-выражения вызовут исключение.
- Return и break ведут себя по-разному в процедурах и лямбдах.
- Далее ведет себя одинаково как в процедурах, так и в лямбдах.
Ссылки
Курс Pluralsight: основы Ruby
Сайт школы vikingscodeschool: http://www.vikingcodeschool.com/falling-in-love-with-ruby/blocks-procs-and-lambdas
rubyguides.com: http://www.rubyguides.com/2016/02/ruby-procs-and-lambdas/