[Перевод] Perl 6 и Rakudo: заметки от 2009 года
Серия статей о Perl 6 и Rakudo — одном из компиляторов, поддерживающих спецификацию Perl6. Эта статья собрана из заметок от 2009 года.Устанавливаем RakudoВ данный момент существует несколько неполных реализаций Perl 6. Самая полная из них — это компилятор Rakudo (скачать).Пользователи git могут создать свою копию при помощи следующих команд:
$ git clone git://github.com/rakudo/rakudo.git $ cd rakudo $ perl Configure.pl --gen-parrot --gen-moar --gen-nqp --backends=parrot, jvm, moar $ make $ make install Как вариант, можно собрать его из исходников, скачав их с github.com/rakudo/rakudo/tree/nom
Для Windows есть готовый установщик бинарников. Версии установщика и исходный код доступны по ссылкеВыполнив команду perl6, вы попадёте в REPL-среду, где сможете поиграть с различными командами языка.
$ perl6 > say «Hello world!»; Hello world! > say (10/7).WHAT (Rat) > say [+] (1…999).grep ({ $_ % 3 == 0 || $_ % 5 == 0 }); 233168 Строчки, начинающиеся с »>» — команды, а все остальные — ответы системы. Первый пример — простая инструкция «say». Второй создаёт рациональное число и запрашивает его тип (Rat). Третий берёт список чисел от 1 до 999, отфильтровывает те, что не делятся на 3 или 5, складывает их и выводит результат.
Красота форматирования В этой статье мы рассмотрим метод .fmtЕсли вам знакома инструкция sprintf, то вам будет проще разобраться с .fmt. Если нет, или если вы подзабыли, как её использовать — почитайте perldoc. Но не углубляйтесь, просто просмотрите.
Итак, .fmt. Вот несколько способов использовать её для форматирования строк и целых чисел.
say 42.fmt ('%+d') # '+42' say 42.fmt ('%4d') # ' 42' say 42.fmt ('%04d') # '0042' say:16<1337f00d>.fmt ('%X') # '1337F00D' Хорошо, но пока что это всего лишь более короткий способ записи sprintf. Однако, при использовании с массивами (точнее, списками) оказывается, что этот метод работает по-другому:
say <эники беники вареники>.fmt # эники беники вареники say <10 11 12>.fmt ('%x') # 'a b c' say <1 2 3>.fmt ('%02d', '; ') # '01; 02; 03' А вот его использование с хешами (мэппингами):
say { foo => 1, bar => 2 }.fmt # 'foo 1 # bar 2'
say { «Яблочки» => 85, «Апельсинчики» => 75 }.fmt ('%s стоят по %d рубликов') # 'Яблочки стоят по 85 рубликов # Апельсинчики стоят по 75 рубликов '
say { «эники» => 1, «беники» => 2, «вареники» => 3 }.fmt ('%s', ' — ') # эники — беники — вареники Правда, в случае с хешем порядок выдачи может отличаться от приведённого. Для пар также существует .fmt, но работает он так же, как и у хешей. .fmt — удобный инструмент для изменения значения или массива значений и приведения его к нужному формату. Похоже на sprintf, но работает и с массивами. Единственный минус — код получается слишком читабельным. Чтобы восстановить репутацию Perl как write-only языка, вот вам новогодний подарочек в виде однострочника, рисующего ёлочку:
$ perl6 -e 'say » «x 9-$_,»#«x$_*2–1 for 0…9,2 xx 3'
# ### ##### ####### ######### ########### ############# ############### ################# ### ### ### Вариант для Windows (требуются другие кавычки):
> perl6.exe -e «say ' 'x 9-$_,'#'x$_*2–1 for 0…9,2 xx 3» Статическая типизация и multi subs В Perl 5 переменные $scalar могли содержать либо ссылку, либо значение. Значением могло быть что угодно — целое число, строка, нецелое число, дата. Гибкость за счёт потери в ясности.В Perl 6 вводится статическая типизация. Если вам нужна переменная определённого типа, вы указываете этот тип при инициализации. К примеру, вот переменная, содержащая целое число:
my Int $days = 24; Другие примеры типов:
my Str $phrase = «Всем привет!»; my Num $pi = 3.141e0; my Rat $other_pi = 22/7; Для использования переменных старого формата, можно либо не указывать тип, либо указать тип Any.
Вторая тема главы — multy subs. Это возможность перегрузить процедуру, используя одно и то же имя для разных случаев. Вот пример:
multi sub identify (Int $x) { return »$x — это целое.»; }
multi sub identify (Str $x) { return qq<"$x" – это строка.>; }
multi sub identify (Int $x, Str $y) { return «Целочисленное $x и строка \»$y\».»; }
multi sub identify (Str $x, Int $y) { return «Строка \»$x\» и целое число $y.»; }
multi sub identify (Int $x, Int $y) { return «Два целых числа — $x и $y.»; }
multi sub identify (Str $x, Str $y) { return «Две строки — \»$x\» и \»$y\».»; }
say identify (42); say identify («Это ж как круто!»); say identify (42,» Это ж как круто!»); say identify («Это ж как круто!», 42); say identify («Это ж как круто!», «Неимоверно!»); say identify (42, 24); Результат:
42 — это целое. «Это ж как круто!» — это строка. Целочисленное 42 и строка » Это ж как круто!». Строка «Это ж как круто!» и целое число 42. Две строки — «Это ж как круто!» и «Неимоверно!». Два целых числа — 42 и 24. Тестирование Авторы perl-модулей привыкли поставлять вместе с модулями, которые они выпускают в мир, набор тестов. Эта традиция поддерживается в perl 6 через особые инструкции.Классический способ написания тестов в perl — выводить данные по протоколу Test Anything Protocol. Но не обязательно делать это вручную — можно использовать модуль.
Допустим, у вас есть функция факториала:
sub fac (Int $n) { [*] 1…$n } Пока неважно, как она работает — мы просто хотим узнать, работает ли она правильно. Давайте проверим:
use v6;
sub fac (Int $n) { [*] 1…$n }
use Test; plan 6;
is fac (0), 1, 'fac (0) работает'; is fac (1), 1, 'fac (1) работает '; is fac (2), 2, 'fac (2) работает '; is fac (3), 6, 'fac (3) работает '; is fac (4), 24, 'fac (4) работает ';
dies_ok { fac ('ёперный театр, да это же строка') }, 'Можно использовать только со строчками'; Запустим:
$ perl6 fac-test.pl 1…6 ok 1 — fac (0) работает ok 2 — fac (1) работает ok 3 — fac (2) работает ok 4 — fac (3) работает ok 5 — fac (4) работает ok 6 — Можно использовать только со строчками Подробнее: use Test; загружает модуль тестирования, plan 6; объявляет запуск шести тестов. Затем идут пять строк в формате «что есть», «что ожидаем получить», «описание». is () сравнивает строки, и поскольку целые числа автоматически преобразуются в строки, всё получается.
В конце dies_ok { $some_code }, $description мы проверяем, что вызов функции с аргументом, не являющимся целым числом, приводит к ошибке.
Выдача теста указывает, что запускаются 6 тестов, и затем в каждой строке выводит результаты тестов (ok — если прошёл, not ok — если провалился), номер теста и описание.
При запуске большого числа тестов не хочется просматривать их все подробно, а хочется увидеть итоги. Команда prove делает именно это:
prove --exec perl6 fac-test.pl fac-test.pl … ok All tests successful. Files=1, Tests=6, 11 wallclock secs (0.02 usr 0.00 sys + 10.26 cusr 0.17 csys = 10.45 CPU) Result: PASS Принято складывать файлы тестов в отдельный каталог t/ и запускать prove рекурсивно на всех файлах из каталога с расширением .t:
prove --exec perl6 -r t Если вы разместите эту строчку в Makefile, то можно будет просто набрать make test для прогона тестов.
Метаоператоры Ранее мы видели интересную реализацию функции факториала: sub fac (Int $n) { [*] 1…$n } Но как это работает? У Perl 6 есть несколько метаоператоров, изменяющих существующие операторы, которые становятся более мощными. Квадратные скобки представляют собой метаоператор reduce, который помещает оператор, указанный внутри скобок, между всеми элементами списка. Например,
[+] 1, $a, 5, $b означает то же самое, что и
1 + $a + 5 + $b Таким образом, мы легко можем суммировать элементы списка:
$sum = [+] @a; # суммировать элементы списка @a Практически все операторы можно поместить в квадратные скобки:
$prod = [*] @a; # перемножение элементов @a
$mean = ([+] @a) / @a; # подсчёт среднего значения @a
$sorted = [<=] @a; # истина, если элементы @a отсортированы по возрастанию
$min = [min] @a, @b; # найти наименьший элемент из всех элементов @a и @b Поэтому, в факториале выражение [*] 1…$n принимает значение перемноженных элементов списка от 1 до $n.
Ещё один метаоператор — hyper. Разместив » или » (или их ASCII аналоги >> и <<) рядом с оператором, мы заставляем его работать на всех элементах списка. Например, следующее выражение делает @c результатом попарного сложения элементов @a и @b:
@c = @a »+» @b; В Perl 5 нам бы пришлось писать нечто вроде
for ($i = 0; $i < @a; $i++) { $c[$i] = $a[$i] + $b[$i]; } hyper используется на разных операторов, включая операторы, определяемые пользователем:
# увеличить все элементы @xyz на 1 @xyz»++
# каждый из элементов @x будет минимальным из соответствующих элементов @a и @b @x = @a «min» @b; Вместо массивов можно использовать скаляры:
# умножить каждый элемент @a на 3.5 @b = @a »*» 3.5;
# умножить каждый элемент @x на $m и добавить $b @y = @x »*» $m »+» $b;
# инвертировать все элементы @x @inv = 1 »/» @x;
# добавить к @last @first и получить @full @full = (@last »~» ', ') »~» @first; Конечно, reduce и hyper можно комбинировать:
# сумма квадратов элементов @x $sumsq = [+] (@x »**» 2); Есть ещё много метаоператоров, например X (cross), R (reverse), S (sequential). Вообще говоря, операторы вроде +=, *=, ~= уже являются мета-формами операторов, к которым добавляется знак равенства:
$a += 5; # то же, что и $a = $a + 5; $b //= 7; # то же, что и $b = $b // 7; $c min= $d; # то же, что и $c = $c min $d; Выходим в гиперпространство
Перед тем, как мы продолжим изучение метаоператоров, введём вспомогательную функцию lsay, которая выводит красиво отформатированные списки. Определяя её через our вы затем сможете использовать её в REPL-среде:
our sub lsay (@a) { @a.perl.say } Начнём с простого: складываем два списка одинаковой длины:
> lsay (1, 2, 3, 4) <<+>> (3, 1, 3, 1) [4, 3, 6, 5] > lsay (1, 2, 3, 4) >>+<< (3, 1, 3, 1) [4, 3, 6, 5] Если длины списков совпадают, оба варианта записи идентичны. Но если их длины отличаются:
> lsay (1, 2, 3, 4) <<+>> (3, 1) [4, 3, 4, 5] > lsay (1, 2, 3, 4) >>+<< (3, 1) # не работает Правило такое: то, на что указывает острый конец гипероператора, может быть продлено, если оно короче того, что находится на другом его конце. Продление происходит через повторение последнего элемента списка. То, на что указывает «тупой» конец, продлению не подлежит. Возможны все комбинации, например, когда продлению подлежит только левая сторона (<<+<<), только правая (>>+>>), обе стороны (<<+>>), или никакая из сторон (>>+<<). Одиночные скаляры тоже можно продлять:
> lsay (1, 2, 3, 4) >>+>> 2 [3, 4, 5, 6] > lsay 3 <<+<< (1, 2, 3, 4) [4, 5, 6, 7] Это азы использования гипероператоров. Также их можно использовать с постфиксными и префиксными операторами:
> lsay ~<<(1, 2, 3, 4) ["1", "2", "3", "4"] > my @a= (1, 2, 3, 4); @a>>++; lsay @a; [2, 3, 4, 5] Также возможно:
> lsay (0, pi/4, pi/2, pi, 2*pi)>>.sin [0, 0.707106781186547, 1, 1.22464679914735e-16, -2.44929359829471e-16] > lsay (-1, 0, 3, 42)>>.Str [»-1»,»0»,»3»,»42»] В данном случае >>. вызывает метод у каждого элемента списка.
Если вам хочется написать @array>>.say, то лучше не надо. Использование гипероператоров подразумевает, что операцию возможно выполнять параллельно, а порядок операций на списке не фиксирован.
Гипероператоры работают не только со встроенными операторами. Можно определить свой оператор, и с ним они также будут работать. Они должны работать (но пока не работают) с in-place операторами — например, инструкция @a >>/=>> 2 должна поделить весь массив на 2. Они работают с многомерными списками, деревьями и хешами. Интересным примером использования гипероператоров является класс Vectorgithub.com/LastOfTheCarelessMen/Vector/blob/master/lib/Vector.pmкоторый представляет реализацию многомерных векторов без единого цикла.
Циклы Любому программисту известно, насколько полезны циклы. Частым примером использования циклов является цикл foreach для прохода по массивам. Именно таким ключевым словом мы пользовались в Perl 5, хотя можно было использовать и for, больше напоминающее стиль C.В Perl 6 всё по-другому.
Теперь для прохода по спискам используется for. foreach больше нет, а для C-стиля используется слово loop. Пока мы рассмотрим только for, которая представляет собой новое, гибкое и мощное свойство языка:
for 1, 2, 3, 4 { .say } Сразу заметно отсутствие скобок вокруг списка. Обычно в Perl 6 нужно меньше скобок, чем в Perl 5. Переменная по умолчанию, как и в Perl 5, это $_. Вызов метода без указания переменной означает вызов метода $_, то есть в нашем случае — $_.say. Нельзя использовать say без аргументов — нужно писать либо .say, либо $_.say
Вместо простого блока можно использовать «заострённый» блок, позволяющий задать имя переменной цикла:
for 1, 2, 3, 4 → $i { $i.say } «Заострённый» блок напоминает анонимную процедуру, он только не ловит исключения. И если вы напишете return внутри такого блока, то произойдёт выход из всей процедуры, которая его вызвала. Такие блоки принимают больше одного параметра. А что произойдёт, если написать так:
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. Можно было бы написать 0…$n-1 или использовать конструктор промежутков 0…^$n, но в Perl 6 есть и более короткий способ через перфикс ^:
for ^4 { .say } На выходе получим:
0 1 2 3 Причин использования циклов в стиле C — нужно знать, на каком из элементов списка мы сейчас находимся, или нужно проходить по нескольким массивам одновременно. У Perl 6 есть и для этого короткая запись через оператор Z (zip):
for @array1 Z @array2 → $one, $two { … } Если у обоих массивов одинаковая длина, $one пройдёт по всем элементам @array1, а $two по всем соответствующим элементам @array2. Если длина разная, цикл останавливается, дойдя до конца наиболее короткого. Таким образом, можно включить индекс массива в цикл вот так:
for ^Inf Z @array → $index, $item { … } Если бесконечные списки вам не по душе:
for ^@array.elems Z @array → $index, $item { … } что приведёт к тому же результату, но самый элегантный вариант — это
for @array.kv → $index, $item { … } array.kv возвращает ключи и значения, а для массива ключи — это как раз индексы элементов.
Таким образом можно проходить хоть по четырём массивам одновременно:
for @one Z @two Z @three Z @four → $one, $two, $three, $four { … } Ограничения параметров и .comb Как статические типы ограничивают значения переменной, так и ограничения (constraints) позволяют регулировать работу процедур и методов. Во многих ЯП необходимо передавать параметры в процедуру и проверять получаемые значения. С ограничениями проверку можно вести прямо при объявлении. Пример: нам не нужны чётные числа. В Perl 5 можно было бы написать так: sub very_odd { my $odd = shift; unless ($odd % 2) { return undef; } # Работаем с нечётным числом } В Perl 6 можно сделать проще:
sub very_odd (Int $odd where {$odd % 2}) { # Работаем с нечётным числом } Если вызвать very_odd с чётным параметром, вы получите ошибку. Для удобства можно перезагрузить процедуры, и работать с любыми числами:
multi sub very_odd (Int $odd where {$odd % 2}) { # Работаем с нечётным числом } multi sub very_odd (Int $odd) { return Bool: False; } Ограничения параметров удобно использовать в паре с методом .comb. Что такое .comb? (comb — расчёска). В случае с волосами и расчёской вы разделяете пряди и укладываете их на голове. .comb — это противоположность .split. Если последний метод позволяет разделять строку по тем элементам, которые вам не нужны, то .comb разделяет её по нужным элементам. Вот простой пример:
say «Perl 6 Advent».comb (/
Но .comb гораздо мощнее. После того, как вы причесали строку, вы можете манипулировать прядями. Если у вас есть строка из символов ASCII, вы можете использовать гипероператоры, чтобы заменить каждый кусочек на ASCII-эквивалент:
say »5065726C36».comb (/
say »5065726C36».comb (/
А вот задачка посложнее: представляю вам древний шифр Цезаря через ограничения параметров, .comb и .map
use v6;
sub rotate_one (Str $c where { $c.chars == 1 }, Int $n) {
return $c if $c!~~ /
my $i = 0; $i += $_ for @_; $i то это будет работать в Perl 5. В Perl 6 так же, как и в Perl 5, параметры, переданные в процедуру, доступны через массив @_. Система очень гибкая и не накладывает ограничений на параметры. Но это довольно муторный процесс, особенно при необходимости проверить:
sub grade_essay { my ($essay, $grade) = @_; die 'Первый аргумент должен иметь тип Essay' unless $essay ~~ Essay; die «Второй аргумент должен быть целым от 0 до 5' unless $grade ~~ Int && $grade ~~ 0…5;
%grades{$essay} = $grade; } В Perl 5 надо было писать isa вместо ~~, и $grades вместо %grades, но только и всего. Теперь взгляните и ужаснитесь, как много ручных проверок пришлось бы провести. Чувствуете? То-то.
В Perl 5 этим занимаются разные удобные модули со CPAN, к примеру Sub: Signatures или MooseX: Declare.
В Perl 6 доступны и другие способы. Например, этот пример можно записать так:
sub grade_essay (Essay $essay, Int $grade where 0…5) { %grades{$essay} = $grade; } Другое дело, и без всяких сторонних модулей. Иногда удобно задавать значения по умолчанию:
sub entreat ($message = 'Ну пазалуста!', $times = 1) { say $message for ^$times; } Этим значениям не обязательно быть константами, а ещё они могут включать предыдущие параметры:
sub xml_tag ($tag, $endtag = matching_tag ($tag)) {…} Если значение по умолчанию не задано, отметьте параметр как необязательный знаком вопроса:
sub deactivate (PowerPlant $plant, Str $comment?) { $plant.initiate_shutdown_sequence (); say $comment if $comment; } Что особенно круто, на параметры можно ссылаться по имени, и передавать их в любом порядке. Никогда не мог запомнить последовательность параметров:
sub draw_line ($x1, $y1, $x2, $y2) { … }
draw_line ($x1, $y1, $x2, $y2); # ух, на этот раз всё верно. draw_line ($x1, $x2, $y1, $y2); # блин! :-/ А так можно ссылаться на них по имени:
draw_line (: x1($x1), : y1($y1), : x2($x2), : y2($y2)); # работает draw_line (: x1($x1), : x2($x2), : y1($y1), : y2($y2)); # и так работает! Двоеточие означает «сейчас будет именованный параметр», а всё вместе будет: имя_параметра ($переданная_переменная). Когда имена параметров и переменных совпадают, можно использовать краткую запись:
draw_line (:$x1, :$y1, :$x2, :$y2); # работает! draw_line (:$x1, :$x2, :$y1, :$y2); # и так работает! Если автор какого-либо API захочет, чтобы все использовали именованные параметры, ему необходимо будет указать двоеточия в объявлении функции:
sub draw_line (:$x1, :$y1, :$x2, :$y2) { … } # необязательные именованные параметры Именованные параметры не обязательны по умолчанию. Иначе говоря, верхний пример эквивалентен следующему:
sub draw_line (:$x1?, :$y1?, :$x2?, :$y2?) { … } # необязательные именованные параметры Если вам нужно сделать параметры обязательными, используйте восклицательный знак:
sub draw_line (:$x1!, :$y1!, :$x2!, :$y2!) { … } # обязательные именованные параметры Теперь их необходимо передавать.
А как насчёт переменного количества параметров? Легко: сделайте параметром массив, которому предшествует звёздочка:
sub sum (*@terms) { [+] @terms } say sum 100, 20, 3; # 123 Получается, что если вы не задаёте ограничения на параметры будущей функции, то она получает ограничения по умолчанию *@_. Что означает — отсутствие ограничений, или эмуляция поведения Perl 5.
Но массив со звездой получает только расположенные в определённом порядке параметры. Если вам нужно передавать именованные параметры, используйте хеш:
sub detect_nonfoos (:$foo!, *%nonfoos) { say «Кроме 'foo' вы передали », %nonfoos.keys.fmt (»'%s; }
detect_nonfoos (: foo (1), : bar (2), : baz (3)); # Кроме 'foo' вы передали 'bar', 'baz' Стоит отметить, что вы можете передавать именованные параметры на манер хеша:
detect_nonfoos (foo => 1, bar => 2, baz => 3); # Кроме 'foo' вы передали 'bar', 'baz' Ещё одно отличие от Perl 5: по умолчанию, параметры предназначены только для чтения:
sub increase_by_one ($n) { ++$n }
my $value = 5; increase_by_one ($value); # хрясь Одна из причин — эффективность. Оптимизаторам нравятся переменные только для чтения. Вторая — воспитание правильных привычек в программисте. Функциональное программирование хорошо и для оптимизатора и для души.
Чтобы верхний пример заработал, нужно записать его так:
sub increase_by_one ($n is rw) { ++$n }
my $value = 5; say increase_by_one ($value); # 6 Иногда это подходит, но иногда проще изменять копию параметра:
sub format_name ($first, $middle is copy, $last) { $middle .= substr (0, 1); »$first $middle. $last» } Оригинальная переменная останется неизменной.
В Perl 6 передача массива или хеша не выравнивает аргументы по умолчанию. Вместо этого для принудительного выравнивания нужно использовать »|»:
sub list_names ($x, $y, $z) { »$x, $y and $z» }
my @ducklings =
Кроме массивов и хешей возможно передавать блоки кода:
sub traverse_inorder (TreeNode $n, &action) { traverse_inorder ($n.left, &action) if $n.left; action ($n); traverse_inorder ($n.right, &action) if $n.right; } Три символа выступают в роли ограничителей типов:
@ Array (позиционные)% Hash (ассоциативные)& Code (вызываемые)
$ — параметр без ограничений.
Не попадитесь в ловушку, пытаясь назначить тип дважды — через имя типа и через символ:
sub f (Array @a) { … } # неправильно, если только вам не нужен массив массивов sub f (@a) { … } # вот, что вы имели в виду sub f (Int @a) { … } # массив целых чисел Если вы дочитали до этого места, вы заслуживаете ещё один однострочник:
$ perl6 -e '.fmt (»%b»).trans (»01» => » #»).say for <734043054508967647390469416144647854399310>.comb (/.**7/)' ### ## ### # # ## # ## # # ### # # ## # #### # #### # # # # # # # # # # # ## # ## ### История с регулярками Давным-давно в не самом далёком королевстве, ученик программиста Perl 6 по имени Тим работал над простой проблемой парсинга. Его босс, мистер С, задал ему парсинг логов, содержащих инвентаризационную информацию, чтобы удостовериться, что они содержат только допустимые строки. Допустимые строки должны выглядеть так: <номер запчасти> <количество> <цвет > <описание> Ученик, немного знакомый с регулярками, написал красивую регулярочку для определения допустимых строчек. Код выглядел так:
next unless $line ~~ / ^^ \d+ \s+ \d+ \s+ \S+ \s+ \N* $$ / Оператор ~~ проверяет регулярку справа относительно скаляра слева. В регулярке, ^^ означает начало строки, \d+ — не менее одной цифры, \S+ — не менее одного непробельного символа, \N* любое количество символов, не являющихся переводом строки, \s+ пробелы и $$ конец строки. В Perl 6 эти символы можно разделять пробелами для улучшения читаемости. И всё было замечательно.Но затем мистер С решил, что неплохо было бы извлекать информацию из логов, а не просто проверять его. Тим подумал, что проблем нет и надо просто добавить захватывающие скобки. Так он и сделал:
next unless $line ~~ / ^^ (\d+) \s+ (\d+) \s+ (\S+) \s+ (\N*) $$ / После совпадения содержимое каждой пары скобок доступно через запись $/[0], $[1] и т.д. Или через переменные $0, $1, $2, и т.д. Тим был счастлив, мистер С тоже.
Но потом оказалось, что на некоторых строках цвет не был отделён от описания. Такие строки выглядели следующим образом:
Сначала Тим попробовал именовать части регулярки. Посмотрев на описание регулярок в Perl 6, Тим обнаружил, что может сделать такую запись:
next unless $line ~~ / ^^ $
next unless $line ~~ / ^^
$
Тим был воодушевлён. Он показал код мистеру С, и тот тоже воодушевился! «Отлично сработано, Тим!»,- сказал мистер С. Все были счастливы, а Тим светился от гордости.
Однако затем он критически присмотрелся к своей работе. У некоторых строк цвет задавался как »(color)» or »(color)» or »(color)». В результате регулярка причисляла такие цвета к описанию, а переменную $ не задавала вообще. Это было неприемлемо. Тим переписал регулярку, добавив туда ещё \s*:
next unless $line ~~ / ^^
$
В этот раз пользователь под именем PerlJam сказал: «Почему бы тебе не поместить свою регулярку в грамматику? Ведь ты практически этим и занимаешься, назначая каждому кусочку свою переменную». «Щито?»,- подумал Тим. Он не имел понятия, о чём PerlJam говорит. После недолгой беседы Тим вроде бы понял, что имеется в виду, поблагодарил пользователя и уселся писать код. На этот раз регулярка исчезла и превратилась в грамматику. Вот, как она выглядела:
grammar Inventory {
regex product { \d+ }
regex quantity { \d+ }
regex color { \S+ }
regex description { \N* }
regex TOP { ^^
# …, а затем, в том месте кода, где проверяется совпадение: next unless Inventory.parse ($line); «Что ж,- подумал Тим,- на этот раз всё организовано неплохо».
Каждая из прошлых переменных превратилась в свою регулярку внутри грамматики. В регулярке Perl 6 именованные регулярки добавляются к проверке через заключение их в угловые скобки. Особая регулярка TOP используется при вызове Grammar.parse со скаляром. А поведение получается таким же — найденная часть выражения сохраняется в именованной переменной.
И хотя совершенству нет предела, Тим и мистер С были очень довольны результатом.
Конец!
Классы, атрибуты, методы, и прочие Как записывать класс в новой объектной модели Perl 6: class Dog { has $.name; method bark ($times) { say «w00f!» x $times; } } Начинаем с ключевого слова class. Для знающих Perl 5 class чем-то похож на package, но «из коробки» даёт кучу семантических возможностей.
Затем мы использовали ключевое слово has, объявляя атрибут, у которого есть метод-акцессор. Точка между $ и name — это твиджил, который сообщает об особенностях доступа к переменной. Твиджил-точка означает «атрибут + акцессор». Ещё варианты:
has $! name; # приватный, доступный только из класса has $.name is rw; # акцессор только для чтения Затем объявлен метод через ключевое слово method. Метод — как процедура, только со своей записью в таблице методов класса. Он доступен для вызова через $self.
Все классы наследуют конструктора по умолчанию по имени new, который назначает именованные параметры атрибутам. Для получения экземпляра класса можно написать так:
my $fido = Dog.new (name => 'Fido'); say $fido.name; # Fido $fido.bark (3); # w00f! w00f! w00f! Вызов методов осуществляется точкой вместо стрелочки в Perl 5. Она на 50% короче и знакома программистам других языков.
Конечно, есть и наследование. Вот так мы можем создать класс щенка:
class Puppy is Dog { method bark ($times) { say «yap!» x $times; } } Есть и делегирование:
class DogWalker { has $.name; has Dog $.dog handles (dog_name => 'name'); } my $bob = DogWalker.new (name => 'Bob', dog => $fido); say $bob.name; # Bob say $bob.dog_name; # Fido Здесь мы объявляем, что вызовы метода dog_name класса DogWalker переадресовываются методу name класса Dog.
Под слоями всей этой красоты есть мета-модель. Классы, атрибуты и методы представлены через мета-объекты. Вот так можно работать с объектами во время исполнения:
for Dog.^methods (: local) → $meth { say «Dog has a method » ~ $meth.name; } Оператор .^ является вариантом., но он вызывает метакласс — объект, представляющий класс. Здесь мы просим у него дать список методов, определённых в классе (: local исключает методы, унаследованные от других классов). А получаем мы не просто список имён, а список объектов Method. Мы могли бы таким способом вызвать сам метод, но в данном случае просто выводим его имя.
Любители мета-программирования, желающие расширять синтаксис Perl 6, будут в восторге, узнав что использование ключевого слова method на самом деле приводит к вызову add_method из мета-класса. Поэтому в Perl 6 есть не только мощный синтаксис для описания объектов, но ещё и возможность расширять его для тех случаев, которые мы пока ещё не предусмотрели.
Модули и экспорт Чтобы создать библиотеку в Perl 6, нужно использовать ключевое слово module: module Fancy: Utilities { sub lolgreet ($who) { say «O HAI » ~ uc $who; } } Разместите это в файл Fancy/Utilities.pm где-нибудь в $PERL6LIB, и затем сможете использовать это так:
use Fancy: Utilities; Fancy: Utilities: lolgreet ('Tene'); Не особенно удобно. Как и в Perl 5, есть возможность обозначить, что некоторые вещи должны быть доступны в области видимости того кода, который загружает этот модуль. Для этого есть такой синтакс:
# Utilities.pm module Fancy: Utilities { sub lolgreet ($who) is export { say «O HAI » ~ uc $who; } }
# foo.pl use Fancy: Utilities; lolgreet ('Jnthn'); Помеченные «is export» символы экспортируются по умолчанию. Также можно отмечать, что символы экспортируются в рамках именованной группы:
module Fancy: Utilities { sub lolgreet ($who) is export (: lolcat, : greet) { say «O HAI » ~ uc $who; } sub nicegreet ($who) is export (: greet, : DEFAULT) { say «Good morning, $who!»; # Always morning? } sub shortgreet is export (: greet) { say «Hi!»; } sub lolrequest ($item) is export (: lolcat) { say «I CAN HAZ A {uc $item}?»; } } Эти теги можно использовать в загружающем коде, чтобы выбирать, что именно импортировать:
use Fancy: Utilities; # получаем только DEFAULTs use Fancy: Utilities: greet, : lolcat; use Fancy: Utilities: ALL; # берём всё, что можно экспортировать Мульти-процедуры экспортируются по умолчанию, им можно только дать метки по желанию:
multi sub greet (Str $who) { say «Good morning, $who!» } multi sub greet () { say «Hi!» } multi sub greet (Lolcat $who) { say «O HAI » ~ $who.name } Классы являются специализацией модулей, поэтому из них тоже можно что-нибудь экспортировать. В дополнение, можно экспортировать метод, чтобы использовать его как мульти-процедуру. К примеру, следующий код экспортирует метод close из класса IO, чтобы его можно было вызывать как «close ($fh);»
class IO { … method close () is export { … } … } Perl 6 также поддерживает поимённое импортирование символов из библиотеки.
Объединения (junctions) Среди новых фич Perl 6 я больше всего люблю объединения. Я представляю далеко не все варианты их использования, но знаю несколько удобных трюков.Объединения– это переменные, которые могут содержать сразу несколько значений. Звучит странно, но давайте рассмотрим пример. Допустим, вам надо проверить переменную на соответствие одному из вариантов значений:
if $var == 3 || $var == 5 || $var == 7 { … } Никогда не любил эту фигню. Слишком много повторений. При помощи объединения any это можно записать так:
if $var == any (3, 5, 7) { … } В ядре языка есть концепция «автоматическое разделение объединений на потоки» (junctive autothreading). Это значит, что вы почти всегда можете передать объединение туда, где ожидается только одно значение. Код будет выполнен для всех членов объединения, а результат будет комбинацией всех полученных результатов.
В последнем примере == запускается для каждого элемента объединения и сравнивает его с $var. Результат каждого сравнения записывается в новое объединение any, которое затем вычисляется в булевом контексте в инструкции if. В булевом контексте объединение any является истиной, если любой из его членов — истина, поэтому если $var совпадёт с любым из значений объединения, тест будет пройден.
Это может сэкономить код и выглядит довольно красиво. Есть ещё один способ записи объединения any, которое можно сконструировать через оператор |:
if $var == 3×5|7 { … } При необходимости инвертировать результаты теста используется вариант объединения под названием none:
if $var == none (3, 5, 7) { … } Как можно догадаться, none в булевом контексте истинно, только если ни один из его элементов не истинный.
Автоматическое разделение на потоки работает и в других случаях:
my $j = any (1, 2, 3); my $k = $j + 2; Что произойдёт? По аналогии с первым примером, $k будет иметь значение any (3, 4, 5).
Объединения работают также и с «умным» поиском. Есть специальные типы объединений, которые хорошо для этого подходят.
Допустим, у вас есть текстовая строка, и вам надо узнать, совпадает ли она со всеми регулярками из набора:
$string ~~ /
Прелесть объединений в том, что их можно передавать практически любой функции любой библиотеки, и этой функции вовсе не обязательно знать о том, что это объединения (но есть возможность распознать их и работать с ними как-то по-особому). Если у вас есть функция, которая делает умное сравнение чего-либо с каким-то значением, его можно передать как объединение.
Есть ещё полезные штучки, которые можно провернуть при помощи объединений. Присутствует ли значение в списке:
any (@list) == $value Списки легко и непринуждённо работают с объединениями. Например:
all (@list) > 0; # Все ли члены списка больше нуля? all (@a) == any (@b); # Все ли элементы списка @a есть в @b? Рациональные дроби Perl 6 поддерживает рациональные дроби, которые создаются простым способом — делением одного целого на другое. Спервоначалу разглядеть что-либо необычное тут сложно: > say (3/7).WHAT Rat () > say 3/7 0.428571428571429 Преобразование Rat в строку происходит посредством представления числа в виде записи с десятичной точкой. Но Rat используют точное внутреннее представление, а не приближённое, которым довольствуются числа с плавающей точкой вроде Num:
> say (3/7).Num + (2/7).Num + (2/7).Num — 1; -1.11022302462516e-16 > say 3/7 + 2/7 + 2/7 — 1 0 Проще всего узнать, что происходит внутри числа Rat, используя встроенный метод .perl. Он возвращает человеко-читаемую строку, которая через eval превращается в исходный объект:
> say (3/7).perl 3/7 Можно выбрать компоненты Rat:
> say (3/7).numerator 3 > say (3/7).denominator 7 > say (3/7).nude.perl [3, 7] С Rat работают все стандартные числовые операции. Арифметические операции с Rat на выходе по возможности тоже дают Rat, а при невозможности — Num:
> my $a = 1/60000 + 1/60000; say $a.WHAT; say $a; say $a.perl Rat () 3.33333333333333e-05 1/30000 > my $a = 1/60000 + 1/60001; say $a.WHAT; say $a; say $a.perl Num () 3.33330555601851e-05 3.33330555601851e-05 > my $a = cos (1/60000); say $a.WHAT; say $a; say $a.perl Num () 0.999999999861111 0.999999999861111 У Num есть модный метод, выдающий Rat заданного приближения (