Статьи

Учебное пособие по Ruby on Rails: блоки, проки и лямбды

Ruby — это язык с набором мощных функций, наиболее мощными из которых, возможно, являются Blocks, Procs и Lambdas. Короче говоря, эти функции позволяют передавать код методу и выполнять его позже. Несмотря на регулярное использование этих функций, многие разработчики не до конца понимают тонкие различия между ними.

Учебное пособие. При подаче заявления на работу по программированию вам часто будет предложен тест, который поможет определить уровень ваших знаний и опыта по данному предмету. Различные статьи этой серии содержат сжатые решения вопросов, которые вы можете ожидать увидеть в таких тестах.

Блок — это код, который неявно передается методу с помощью фигурных скобок, {...} или do...end синтаксиса. Общепринято использовать {...} для однострочных блоков, а do...end для многострочных. Например, следующие блоки функционально одинаковы:

1
2
3
4
5
6
7
8
9
array = [1,2,3,4]
array.map!
  n * n
end
=> [1, 4, 9, 16]
 
array = [1,2,3,4]
array.map!
=> [1, 4, 9, 16]

Магия за блоком — это ключевое слово yield ; он откладывает выполнение вызывающего метода для оценки блока. Результат блока, если он есть, затем оценивается любым оставшимся кодом в методе. Оператор yield также может принимать параметры, которые затем передаются и оцениваются в блоке. Связав это вместе, простой пример map! Вышеуказанный метод будет следующим:

1
2
3
4
5
6
7
class Array
  def map!
    self.each_with_index do |value, index|
      self[index] = yield(value)
    end
  end
end

Это простое представление map! вызывает метод each_with_index и заменяет элемент с заданным индексом результатом блока. Хотя это тривиальный пример использования блока, он помогает показать yield . Использование блоков в Ruby бесконечно, и мы часто используем их в нашем коде.


Приведенный выше пример демонстрирует незначительное ограничение блоков: они синтаксические и одноразовые. Мы должны перепечатывать блоки каждый раз, когда повторно используем их в разных массивах, но мы можем сохранить блок для последующего использования с помощью объекта Ruby Proc. Мы можем сохранить Proc в переменной, а затем явно передать его любому методу, который принимает вызываемый объект. Переписав приведенный выше пример как Proc, выглядело бы следующим образом:

1
number_squared = Proc.new { |n|

Давайте изменим нашу map! метод для принятия и вызова объекта Proc:

01
02
03
04
05
06
07
08
09
10
11
12
13
class Array
  def map!(proc_object)
    self.each_with_index do |value, index|
      self[index] = proc_object.call(value)
    end
  end
end
 
array = [1,2,3,4]
 
array.map!(number_squared)
 
=> [1, 4, 9, 16]

Обратите внимание, что мы больше не используем ключевое слово yield ; вместо этого мы напрямую используем метод call объекта Proc, передавая ему значение из массива. Мы получаем тот же результат, что и раньше, но мы сохраняем наш блок в переменной для последующего использования.


Лямбда-функции почти идентичны Procs, но с двумя ключевыми отличиями. Во-первых, лямбда проверяет количество получаемых аргументов и возвращает ArgumentError если они не совпадают. Например:

1
2
3
4
5
l = lambda { «I’m a lambda» }
l.call
=> «I’m a lambda»
l.call(‘arg’)
ArgumentError: wrong number of arguments (1 for 0)

Во-вторых, лямбда-выражения обеспечивают минимизирующий возврат — это означает, что когда Proc встречает инструкцию return в своем исполнении, он останавливает метод и возвращает предоставленное значение. Лямбды, с другой стороны, возвращают свое значение методу, позволяя ему продолжить:

01
02
03
04
05
06
07
08
09
10
11
12
def proc_math
  Proc.new { return 1 + 1 }.call
  return 2 + 2
end
 
def lambda_math
  lambda { return 1 + 1 }.call
  return 2 + 2
end
 
proc_math # => 2
lambda_math # => 4

Как видите, proc_math попадает в оператор return внутри Proc и возвращает значение 2 . Напротив, lambda_math пропускает оператор return и вместо этого оценивает 2 + 2.

И последнее замечание: Ruby 1.9 представляет новый «лямбда-синтаксис» «stabby» (обозначается ->), который функционально идентичен традиционному лямбда-синтаксису, но синтаксис «stabby» намного чище.


В этом учебном пособии мы рассмотрели ключевые различия между блоками, Procs и Lambdas:

  • Блоки одноразовые.
  • Procs существуют как объекты.
  • Лямбды имеют строгую проверку аргументов.

Для более глубокого обзора я рекомендую следующие ресурсы: