Нумераторы, итераторы, лямбды

Андрей Васильев

2019

Нумераторы - объекты-итераторы

  • Итераторы предлагают способ взаимодействия с внутренним состоянием объекта через вызов методов
  • Очень сложно таким образом решить задачу синхронной обработки набора коллекций
  • Иногда интересно передать итератор внутрь другого метода
  • Класс Enumerator реализует «внешние итераторы»

Создание нумератора

  • Вызов метода to_enum на коллекции или enum_for с указанием названия итератора
  • Большинство стандартных итераторов возвращают нумераторы, если с ними не ассоциирован блок

Нумераторы являются объектами класса Enumerator, который включает в себя модуль Enumerable, что делает доступным для нумераторов всех «классных» методов

Метод loop

Задачей данного метода является бесконечный вызов блока. Если внутри блока используются нумераторы, то выход будет осуществлён, когда закончатся значения в нумераторе

Нумераторы являются объектами

Метод each_with_index определён в модуле Enumerable

Метод with_index определён в классе Enumerator

Создание нумераторов с enum_for

Методу enum_for можно передать название метода-итератора, который будет предоставлять значения последовательности

Если итератор ожидает аргументов, то их следует передать после имени метода

Создание произвольных нумераторов

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

Бесконечные последовательности

Если генерирующий блок способен предоставлять бесконечное число значений, то его надо указать “ленивым”

Создание собственного нумератора

Хорошей практикой при создании собственного итератора является возвращение нумератора в случае, когда блок не ассоциирован с данным методом

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

Блоки для описания транзакций

Зачастую необходимо выполнять связные действия, например открытый файл обязательно должне быть закрыт

Интересные моменты из примера

  • Методы, начинающиеся со слова self, относятся к классу, а не к объекту класса (“статические”)
  • *args в аргументах метода open_and_process собирает все аргументы в массив args
  • *args в вызове метода open раскрывает содержимое массива и записывает их как аргументы метода
  • Есть также нотация **opts для обработки именованных аргументов
  • Вы можете открыть существующие классы и добавить в них методы. Данная техника на настоящий момент не приветствуется, так как классы - глобальные переменные. Используйте наследование, если это необходимо.

Метод File.open

  • Данный метод уже реализует необходимую функциональность, вы можете ассоциировать с ним блок
  • Метод block_given? проверяет наличие блока и позволяет реализовать альтернативное поведение

Данная техника применяется в итераторах Array, Hash, Enumerable и т.д. для обработки ситуации работы метода с блоком и без него. Если вы не ассоциировали блок с итератором, то он вернёт нумератор

Блоки могут быть объектами

Блоки похожи на анонимные методы, однако с ними можно общаться как с объектами: сохранять в переменные…

Создание блоков-объектов

Блоки представлены классом Proc

Объекты можно вернуть из методов

Лямбда-блоки

Блоки можно создавать с помощью метода lambda

Или использовать краткий синтаксис ->

Отличие лямбд от блоков

  • При вызове лямбды проверяется количество аргументов
  • При вызове блока аргументы обрабатываются так
    • Если блок был вызван с 1 аргументом - массивом, тогда его значения становятся значением параметров блока
    • Если блоку было передано меньше аргументов, чем нужно, то оставшиеся аргументы будут равны nil
    • Если блоку было передано больше аргументов, чем нужно, то оставшиеся параметры будут отброшены

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

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

вывод Не используйте ключевое слово return внутри блоков

Вызов лямбд и блоков

Лямбды и блоки определены в едином классе Proc

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

  • #call(params): action.call(42)
  • #.(params): action.(42)
  • #[params]: action.[42]
  • #yield(params): action.yield(42)

Все формы равносильны между собой. Рекомендуется использовать #call()

Замыкания в блоках

Замыкание - возможность доступа к переменным, объявлённых вне блока

  • Аргумент thing метода n_times находится в области видимости выражений блока
  • При вызове лямбды происходит обращение к thing
  • Вы можете изменять такие переменные, но осторожно

Генераторы на основе лямбд

Можно реализовать простой генератор следующим образом

Синтаксис для создания лямбд

Современным способом описания коротких лямбд является

  • Список аргументов выносится перед телом блока
  • Ключевое слово lambda длиннее ->

Преобразование объектов в блоки

Любой класс, реализующий метод #to_proc может быть преобразован в блок с помощью оператора &

Поддержка со стороны стандартных классов

Символы

Создаёт блок, который будет вызывать метод с именем символа

Хеши

Создаёт блок, который связывает ключи с их значением

Блоки для вызова методов объектов

Метод Object#method(symbol) позволяет создать объект класса Method, который позволяет вызывать данный метод в стиле лямбды

Интерфейс класса Method повторяет в ключевых моментах интерфейс класса Proc и де-факто может использоваться как альтернатива блокам и лямбдам

Композиция лямбд (с версии 2.6)

Классы Proc и Method предоставляют методы << и >>, которые позволяют создать цепочки обработки данных

Ограничением такого подхода является то, чтобы формат передаваемых данных был в точности тем, что ожидают блоки

Каррирование блоков

Объекты классов Proc и Method поддерживают метод curry, который позволяет создавать блоки с предустановленными аргументами