Бесплатный курс по Ruby. Зарегистрируйтесь для отслеживания прогресса →

Ruby: Блоки как объекты

Блоки — одна из ключевых концепций в Ruby, без которой практически не обходится ни один кусок кода. Они очень похожи на обычные лямбда-функции, но имеют свои особые черты. Для хорошего понимания языка и происходящего в коде нужно понимать, как они устроены. Здесь мы немного копнем вглубь.

С одной стороны, у блоков есть особый синтаксис создания и передачи в функции как особого параметра.

# Открытие файла на запись и добавление туда строки
File.open('log.txt', 'w') { |f| f.write "[hexlet] #{Time.now} - User logged in\n" }

С другой стороны, сам блок — это объект, как и всё остальное в языке. Его можно как создать независимо от функции, так и использовать. За блоки в Ruby отвечает класс Proc:

# Немного взрывает мозг то, что блок определяется через свой же синтаксис
square = Proc.new { |x| x**2 }
# Альтернативный синтаксис — proc { |x| x**2 }
square.call(4) # 16

С объектом-блоком можно делать всё то же самое, что и с другими объектами. В этом смысле он ведет себя как анонимная функция в любом языке. Однако, если мы захотим этот объект использовать как блок при передаче в функцию, то ничего не получится:

[1, 2].map square
# ArgumentError (wrong number of arguments (given 1, expected 0))

Хотя мы и имеем дело с блоком, всё же в примере выше он передается в функцию как обычный объект первым параметром. Но метод map() не принимает на вход ничего, кроме блока, поэтому код завершается с ошибкой. Блок, созданный как объект, невозможно напрямую использовать в методах, ожидающих на вход блоки. Для этого нужен специальный синтаксис:

[1, 2].map &square # [1, 4]

Амперсанд, добавленный в начале переменной, содержащей блок, передает этот блок в функцию не как параметр, а как блок. Но тут нас ждет сюрприз:

# Завершится с ошибкой
[1, 2].map() &square

# Сработает
[1, 2].map(&square)

Это поведение не должно вас вводить в заблуждение. Выше мы видим исключительно особенности синтаксиса. Блок выше не передается как параметр, это легко увидеть, если посмотреть на определение метода map(). Он не принимает на вход никаких параметров.

Задание

Реализуйте функцию apply_blocks(data, blocks), которая принимает на вход данные и массив блоков. Эта функция должна по цепочке вызывать блоки, передавая туда результат предыдущего вызова блока:

proc1 = proc { |x| x + 1 }
proc2 = proc { |x| x * 2 }

apply_blocks(5, [proc1, proc2]) # 12
# proc2.call(proc1.call(5))
apply_blocks(5, [proc2, proc1]) # 11
# proc1.call(proc2.call(5))
Упражнение не проходит проверку — что делать? 😶

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе