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.

Проверьте себя
1. Чем lambda отличается от обычного Proc?
ALambda работает быстрее
BLambda строго проверяет число аргументов, а return в ней выходит только из самой лямбды
CLambda нельзя вызвать через .call
DМежду ними нет различий
2. Как работает идиома arr.map(&:upcase)?
A& удаляет элементы
BСимвол :upcase превращается оператором & в блок, вызывающий метод upcase на каждом элементе
CЭто синтаксическая ошибка
D& означает логическое И