Курс языка программирования Raku / Функциональное, конкурентное, реактивное и веб-программирование / Функциональное программирование / Итераторы

Протокол итератора

Всякое значение, по которому можно пройти циклом — список, диапазон, массив, — может выдать вам итератор методом .iterator. У итератора есть один главный метод, pull-one, который при каждом вызове возвращает следующее значение:

my $it = (10, 20, 30).iterator;
say $it.pull-one; # 10
say $it.pull-one; # 20
say $it.pull-one; # 30

Когда значения заканчиваются, pull-one возвращает не настоящий элемент, а особый маркер — IterationEnd. Запрос ещё одного значения у того же $it это и показывает:

say $it.pull-one =:= IterationEnd; # True

Оператор =:= проверяет, что это тот же самый объект, — именно так маркер и распознают. Эта деталь важна внутри цикла, где нужно и сохранить вытянутое значение, и проверить его. Если присвоить его через =, значение попадёт в контейнер, и сравнение станет смотреть на контейнер, а не на IterationEnd. Средство от этого — связать через :=, чтобы переменная попросту и была тем, что вернул pull-one:

my $it = <a b c>.iterator;
loop {
    my $v := $it.pull-one;      # связываем, а не присваиваем
    last if $v =:= IterationEnd;
    say $v;
}

Цикл печатает a, b, c, а затем останавливается. Ровно это цикл for и делает за вас под капотом: он вызывает .iterator у того, по чему вы идёте, и продолжает вызывать pull-one, пока не встретит IterationEnd. Писать это вручную приходится редко, но, сделав так однажды, видишь: Raku на самом деле проходит циклом по итераторам, а не по спискам.

Практика

Пройдите 1 тест по содержанию этой темы.

Навигация по курсу

Итераторы   |   Тест — Протокол итератора


💪 Или перейдите сразу к упражнению этого раздела.