Возникли проблемы с пониманием разницы между рубиновыми блоками, процедурами и ламами. Что такое блоки? В чем разница между 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/