День 7: Циклы для пользы и развлечения
Перевод на русский язык седьмой статьи цикла Perl 6 Advent Calendar.
Любой программист, когда-либо использовавший язык, наверняка знает, что циклы чрезвычайно полезны. В языках, которые предоставляют их, очень часто используют циклы foreach для выполнения итерации массивов или списков. В Perl 5 эти циклы были представлены ключевым словом foreach, хотя, так же можно было писать for, для большей схожестью с C-подобными циклами.
В Perl 6 все изменилось.
В настоящее время для итерации по спискам используется исключительно for. foreach исчез, а C-подобные циклы обрабатываются новым ключевым словом loop. Сегодня мы не будем это обсуждать, но мы собираемся сосредоточиться на новом цикле for, который в сочетании с другими языковыми особенностями Perl 6 предоставляет чрезвычайно гибкую и мощную конструкцию.
Давайте рассмотрим обычный случай.
for 1, 2, 3, 4 { .say }
Здесь сразу заметны некоторые вещи в синтакисе. Нет скобок списка конструкций, которые распространяются на весь Perl 6. В общем, требуется много меньше скобок чем в Perl 5. Так же как и в Perl 5, по умолчанию переменная цикла $_. Вызов метода say без какого-либо применения, так же как и $_.say. Обратите внимание, что в Perl 6 по умолчанию мы не можем вызвать say без аргумента для $_, мы должны использовать .say либо явно указать $_ в качестве аргумента.
Блок не должен быть обычным блоком. Это может быть острый блок (->), который позволяет нам передавать параметр из цикла в переменную.
for 1, 2, 3, 4 -> $i { $i.say }
Острый блок похож на анонимную подпрограмму, за исключением того, что не ловит значения return. Если мы вызовем return внутри острого блока, будет возвращено enclosing routine.
Острый блок может принимать больше одного параметра из списка. Что произойдет если мы это сделаем?
for 1, 2, 3, 4 -> $i, $j { "$i, $j".say }
Теперь, если мы запустим это, то получим:
1 2 3 4
То, что произошло, является итерацией по два элемента из списка. Это работает для любого числа параметров с одним минимальным, который вырождается в использование $_ если мы явно не укажем свой параметр.
Поняв как это работает, что мы можем сделать с перебором списков? Ну конечно же мы можем использовать переменную массива:
for @array { .say }
Хотя во многих простых случаях лучше использовать map:
@array.map: *.say;
Или гипероператор, если порядок и последовательность не имеют значения:
@array».say;
Но ни одна из этих вещей не является сегодня объектом обсуждения.
Мы можем создать список номеров с помощью конструктора диапазона «..«:
for 1..4 { .say }
Зачастую мы хотим создать список из $n чисел начиная с нуля, таких как индекс массива. Мы могли бы написать 0..$n-1 или использовать вариант конструктора диапазона 0..^$n, но Perl 6 предоставляет удобный инструмент в форме «^«:
for ^4 { .say }
Который выведет:
0 1 2 3
Одной из причин, по которой люди часто ссылаются на C-подобный стиль циклов в Perl 5 является необходимость знать что индекс в массиве они находят для каждого элемента, или необходимость перебирать два или больше массивов параллельно. Perl 6 предлагает для этих целей оператор Z (zip-оператор, оператор «застёжка»).
for @array1 Z @array2 -> $one, $two { ... }
Предполагая, что два массива одинаковой длины, в $one будет каждый элемент массива @array1 и в $two будет соответствующий элемент массива @array2. Если они различной длины, то итерация будет остановлена на последнем элементе самого короткого массива.
Зная это, и зная то, что в Perl 6 есть «ленивые» списки генераторов, мы можем легко включить индекс массива в итерацию:
for ^Inf Z @array -> $index, $item { ... }
Хотя, если бесконечные списки нервируют вас,
for ^@array.elems Z @array -> $index, $item { ... }
даст вам тот же результат, но, возможно, самое элегантное представление:
for @array.kv -> $index, $item { ... }
@array.kv возвращает ключи и значения попарно, где ключи массива являются индексами, так что, повторив итерации над ними два раза, мы получим желаемый эффект.
Надеюсь, что этот пост даёт вам представление о гибкости, присущей циклам Perl 6 и как легко они могут быть использованы для решения ряда общих задач. Прежде чем расстаться, я собираюсь ответить на один последний вопрос, который у кого-то возник.
А что, спросите вы, если я захочу перебрать четыре массива за раз?
for @one Z @two Z @three Z @four -> $one, $two, $three, $four { ... }
Вот список ассоциативных инфиксных операторов, то есть – наслаждайтесь.
P.S.: Спасибо товарищу bvp за помощь в переводе.