Procs и лямбды
Блок нельзя сохранить в переменную — он живёт лишь в момент вызова. Чтобы «упаковать» кусок кода в объект и передавать его как обычное значение, нужны Proc и lambda.
Суть: Proc и lambda — это объекты, оборачивающие блок кода, который можно хранить, передавать и вызывать позже через.call; lambda строже проверяет аргументы и иначе ведёт себя сreturn.
Блоки прекрасны, но у них ограничение: метод принимает максимум один блок, и блок нельзя положить в массив или вернуть из метода. Proc снимает это ограничение — это блок, ставший полноценным объектом. Lambda — особый, более «дисциплинированный» вид Proc.
double = ->(x) { x * 2 } # лямбда (стрелочный синтаксис)
puts double.call(5) # => 10
puts double.(5) # => 10 (сокращение)
puts double[5] # => 10 (ещё одно)
square = Proc.new { |x| x * x }
puts square.call(4) # => 16
Разбор: чем lambda отличается от Proc
Два ключевых отличия. Первое — строгость аргументов: lambda ругается, если передать не то число аргументов, а Proc прощает (лишние игнорирует, недостающие делает nil). Второе — return: в lambda он выходит из самой лямбды, а в Proc — из объемлющего метода, что может неожиданно прервать его.
strict = ->(a, b) { a + b }
# strict.call(1) => ArgumentError: lambda требует ровно 2 аргумента
loose = Proc.new { |a, b| "#{a}-#{b}" }
puts loose.call(1) # => "1-" (b стал nil, ошибки нет)
puts loose.call(1,2,3) # => "1-2" (лишний 3 проигнорирован)
Как работает под капотом: &-преобразование
Связь блоков и Proc — оператор &. В параметре метода &block ловит переданный блок в Proc-объект. А при вызове &proc разворачивает Proc обратно в блок. Именно поэтому работает знаменитая идиома &:upcase: символ превращается в блок, который вызывает метод.
метод(&block) вызов(&proc)
| |
блок --> ЗАХВАТ Proc --> РАЗВОРОТ в блок
| |
block: Proc-объект передаётся методу как блок
["a","b"].map(&:upcase)
|
:upcase --> Proc { |x| x.upcase } --> блок --> ["A","B"]
Та же идея «функция как значение» на Python — это lambda и обычные функции:
# Та же логика на Python ▶
double = lambda x: x * 2
print(double(5)) # 10
funcs = [double, lambda x: x + 1] # функции в списке
print(funcs[1](10)) # 11
Частые ошибки
- Ждать от Proc строгости lambda. Proc молча проглотит неверное число аргументов — баг будет тихим.
- return из Proc. Внутри Proc он выходит из метода целиком, а не из Proc — частый источник неожиданного поведения.
- Забывать .call. Сам Proc не выполняется при упоминании — его нужно явно вызвать через
.call,.()или[].
Best practices
- По умолчанию выбирайте lambda: строгая проверка аргументов ловит ошибки раньше, а поведение
returnпредсказуемо. - Используйте стрелочный синтаксис
->(x) { }— он короткий и общепринятый. - Активно применяйте
&:methodдля лаконичных вызовов:names.map(&:capitalize)вместоnames.map { |n| n.capitalize }.
Глубже: замыкания и захват окружения
Самое важное и одновременно самое недооценённое свойство блоков, Proc и лямбд — это замыкание (closure). Когда вы создаёте Proc или лямбду, она «запоминает» окружение, в котором родилась: локальные переменные, доступные в тот момент, остаются доступны внутри неё даже после того, как метод-создатель завершился. Это позволяет создавать функции, «настроенные» на конкретные данные. Например, метод может вернуть лямбду, которая помнит переданный ей коэффициент, и потом эта лямбда будет умножать на него любое число. Замыкания лежат в основе многих элегантных приёмов: фабрики функций, отложенные вычисления, колбэки, которые помнят контекст вызова. Понимание того, что лямбда — это не просто «кусок кода», а «кусок кода вместе с захваченным окружением», поднимает вас на новый уровень владения языком. Эта же идея объясняет, почему блоки в each видят переменные окружающего метода: они тоже замыкания. Замыкания — фундаментальная концепция, общая для Ruby, JavaScript, Python и многих других языков, и однажды понятая, она остаётся с вами навсегда.
Итог. Proc и lambda — это блоки, ставшие объектами: их можно хранить и передавать, вызывая через .call. Lambda строга к аргументам и локальна в return, Proc — снисходительнее. Оператор & связывает блоки и Proc, обеспечивая идиому &:method.