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 бесконечно, и мы часто используем их в нашем коде.
Procs
Приведенный выше пример демонстрирует незначительное ограничение блоков: они синтаксические и одноразовые. Мы должны перепечатывать блоки каждый раз, когда повторно используем их в разных массивах, но мы можем сохранить блок для последующего использования с помощью объекта 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 существуют как объекты.
- Лямбды имеют строгую проверку аргументов.
Для более глубокого обзора я рекомендую следующие ресурсы: