новости
статьи
.программирование

от Perl 5 к Perl 6

введение

краткое содержание

- Изучайте Perl 6 (если вы уже знаете Perl 5).

- Научитесь любить Perl 6.

- Поймите почему.


Perl 6 еще недостаточно документирован. Не удивительно, потому что (в отличие от спецификации) написание компилятора для Perl 6 представляется гораздо более приоритетной задачей, чем написание документации, ориентированной на пользователя.

К сожалению, это означает, что научиться Perl 6 не просто, и нужно быть сильно увлеченным Perl 6, чтобы найти причины изучить его, пользуясь спецификацией, IRC-каналами и набором тестов.

Проект, который я предварительно назвал «От Perl 5 к Perl 6» (из-за отсутствия лучшего названия), — попытка заполнить этот пробел циклом коротких статей.

В каждом уроке охвачена довольно ограниченная тема и объясняются два или три наиболее важных момента с помощью очень коротких примеров. Кроме того, я пытаюсь объяснить, почему что-то меняется от Perl 5 к Perl 6, и почему это важно. Я также надеюсь, что знания, которые вы получите от прочтения этих уроков, будут достаточными для базового понимания Synopsis — канонического источника всей мудрости Perl 6.
Чтобы упростить чтение, каждый урок не будет превышать 200 строк или 1000 слов (но это не жесткое ограничение).

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

Это не руководство по трансформированию программ Perl 5 в программы на Perl 6. Также это не исчерпывающий список отличий. Если вы ищите именно это, посмотрите http://svn.pugscode.org/pugs/docs/Perl6/Perl5/Differences.pod.

Perl 6 — это спецификация языка, и существуют несколько компиляторов, которые пишут для того, чтобы воплотить Perl 6. Частично это уже удалось.

Pugs

Pugs — компилятор Perl 6, написанный на языке Haskell. Проект начат Одри Танг, и она почти довела его до завершения. Если считать число реализованных моментов, то это определенно наиболее полная реализация на сентябрь 2008.

Чтобы собрать и протестировать pugs, необходимо вначале установить компилятор GHC 6.8, а затем выполнить

svn co http://svn.pugscode.org/pugs
cd pugs
perl Makefile.PL
make
make test


Это установит локально некоторые зависимости, необходимые Haskell, а затем соберет pugs. Для запуска make test возможно потребуется установить некоторые модули Perl 5, это можно сделать с помощью вызова cpan Task::Smoke.

Pugs не развивался в течение последнего года, но Одри анонсировала, что она продолжит разработку после выхода GHC 6.10, и уже заметно привела в порядок процедуру установки.

Pugs может разобрать многие общие конструкции, поддерживает объекты, простые регулярные выражение, многие управляющие структуры, основные пользовательских операторов и макросов, многие встроенные функции, контексты (кроме контекста среза), объединения, основы множественной диспетчеризации и метаоператор редуцирования. Он проходит около 16 000 тестов из тестового набора Perl 6.

Rakudo

Rakudo — компилятор Perl 6, основанный на Parrot. Основной архитектор — Патрик Мишо (Patrick Michaud), многие возможности реализованы Джонатаном Вортингтоном (Jonathan Worthington).

Rakudo находится в svn-репозитории Parrot, для сборки необходимо выполнить следующее:

svn co https://svn.perl.org/parrot/trunk parrot
perl Configure.pl && make
cd languages/perl6/
make perl6


Процесс разработки очень Rakudo очень активен. Во время написания статьи он проходит около 2300 тестов из официального набора тестов. За прогрессом выполненных тестов можно наблюдать на сайте http://rakudo.de/.

Rakudo реализует большинство управляющих структур, большую часть синтаксиса для числовых литералов, интерполяции скаляров и замыканий, цепочечных операторов, блоков BEGIN и END, pointy-подпрограмм, именованных, необязательных и «заглатывающих» (slurpy) аргументов, основы объединений и грамматик.

Elf


Митчел Чарити (Mitchell Charity) начала проект elf, это компилятор, написанный на Perl 6 с помощью грамматик, написанных на Ruby. Сейчас он имеет бекенд Perl 5, планируются и другие.

Он находится в репозитории Pugs, если вы уже его загрузили, то перейдите в каталог misc/elf/ и выполните ./elf_f $filename. Потребуется ruby-1.9 и некоторые модули Perl, о которых elf сообщит, если их не обнаружит.

Разработка elf идет с пиками активности, за которыми следуют недели низкой активности, или вообще тишины.

Он разбирает более 70% тестового набора, но реализует в основном особенности, которые легко эмулировать с помощью Perl 5, и проходит около 700 тестов из набора.

KindaPerl6

Флавио Глок (Flavio Glock) начал проект KindaPerl6 (сокращенно kp6). Этот компилятор сейчас ожидает, когда появится более быстрый бекенд, сейчас он слишком медленный для того, чтобы пробовать применять kp6.
В kp6 реализованы объекты, грамматики, и некоторые отдельные вещи типа "ленивого" gather/take. Реализован и блок C, что было одной из задач дизайна.

v6.pm

v6 — это фильтр исходных кодов на Perl 5. Он был написан Флавио Глоком, и поддерживает основы Perl 6 и грамматики. Он весьма стабилен и быстрый, и время от времени дорабатывается. Находится на CPAN и в каталоге perl5/*/ в репозитории Pugs.

SMOP

SMOP обозначает простое мета-объектное программирование (Simple Meta Object Programming) и не подразумевает реализацию Perl 6 целиком, а задуман как бекенд (несколько напоминая Parrot, то сильно отличаясь от него по дизайну и функционалу). В отличие от других реализаций, главная цель здесь - реализовать мощные возможности Perl 6 по части возможностей мета-объектного программирования, то есть возможности подключать разные объектные системы.

SMOP реализован на C и на нескольких специфичных языках. Он был разработан и реализован Даниэлем Русо (Daniel Ruoso) при помощи Юваля Когмана (Yuval Kogman), отвечающего за дизайн, и Павла Муриаса (Pavel Murias), делавшего реализацию и DSL. Разработку поддерживал грант Perl Foundation. Затем SMOP будет использоваться как бекенд или для elf, или для kp6, и возможно также для Pugs.

STD.pm

Ларри Уолл написал грамматику для Perl 6 на Perl 6. Он написал и скрипт gimme5, который транслирует эту грамматику в Perl 5. Скрипт способен разобрать почти все, что является валидным Perl 6, включая полный набор тестов (за небольшими исключением, когда Ларри время от времени случайно что-то ломает).

STD.pm находится в репозитории Pugs, и может быть запущен и протестирован с помощью perl-5.10.0, установленном в /usr/local/bin/perl и некоторым числом Perl-модулей (таких как YAML::XS и Moose):

cd src/perl6/
make
make testt # предупреждение: занимает около 80 минут,
# и генерирует 3 ГБ файлов в lex/
./tryfile $your_file
perl STD5_dump_match $your_file


STD.pm корректно разбирает пользовательские операторы и жалуется на пропущенные объявления переменных и несуществующие подпрограммы.

причины

Многие спрашивают, зачем нам нужно так много разных реализаций, не будет ли лучше вместо этого сфокусироваться на одной.

Есть как минимум три ответа на этот вопрос.

Во-первых, свободные программисты так не работают. Люди иногда хотят или начать что-то с теми инструментами, которые им нравятся, либо им хочется думать, что какой-то аспект Perl 6 недостаточно следует дизайну в существующих реализациях. Они начинают новый проект.

Второй возможный ответ - проекты охватывают разные области разностороннего языка Perl 6: SMOP занимается мета-объектным программированием, Rakudo и Parrot заботятся об эффективной взаимной совместимости языков и платформо-независимости, kp6 интересует блок BEGIN, а Pugs был первой реализацией, с помощью которой можно было поисследовать синтаксис и многие свойства языка.

Третий ответ состоит в том, что мы не хотим, чтобы один проект оказался в то же время и единственной неудачей. Если существует несколько реализаций, и в одна из них оказывается проблемной по непредсказуемым причинам (техническим, правовым, личным и другим), у нас будет запасной аэродром.

ссылки по теме

Pugs: http://www.pugscode.org/, http://pugs.blogs.com/pugs/2008/07/pugshs-is-back.html, http://pugs.blogspot.com, исходный код:
http://svn.pugscode.org/pugs.
Rakudo: http://www.parrotcode.org/, http://www.rakudo.org/, исходный код: https://svn.perl.org/parrot/trunk.

Исходные коды остальных проектов находятся в каталогах репозитория Pugs. Elf: misc/elf/. KindaPerl6: v6/v6-KindaPerl6. v6.pm: perl5/. STD.pm: src/perl6/.

урок 1. Строки, массивы, хеши

краткое содержание

my $five = 5;
print "интерполированная строка, такая же, как в perl $five\n";
say 'say() добавляет перенос строки при выводе, точно так же, как в perl 5.10';
my @array = 1, 2, 3, 'foo';
my $sum = @array[0] + @array[1];
if $sum > @array[2] {
say "не выполено";
}
my $number_of_elems = @array.elems; # или +@array
my $last_item = @array[*-1];
my %hash = foo => 1, bar => 2, baz => 3;
say %hash{'bar'}; # 2
say %hash<bar>; # тоже самое, только с автоматическим квотированием
# это ошибка: %hash{bar
}


Perl 6 — почти как perl 5, только лучше. Инструкции разделяются точками с запятыми. После последней инструкции в блоке или после закрывающей фигурной скобки в конце строки точка с запятой не обязательна.

Имена переменных, как и раньше, начинаются с сигилов (например, $, @, %), и многие встроенные функции Perl 5 почти не были изменены в Perl 6.

строки

Строки записываются с помощью двойных (в этом случае строка интерполируется) или одинарных кавычек. Экранирование с помощью обратного слеша работает так же, как и в Perl 5.

Однако правила интерполяции немного изменились. Вот как это работает:

my $scalar = 6;
my @array = 1, 2, 3;
say "Perl $scalar"; # 'Perl 6'
say "An @array[]"; # 'An 1 2 3'
say "@array[1]"; # '2'
say "Code: { $scalar * 2 }" # 'Code: 12'


Массивы и хеши интерполируется только в том случае, если за ними указан индекс или ключ; пустые индексы интерполируют структуру данных целиком. Блок в фигурных кавычках выполняется как блок кода, и результат интерполируется.

массивы

Переменные массивов по-прежнему начинаются с символа @. И это всегда так, даже если присутствует индекс.

my @a = 5, 1, 2; # скобки более не нужны
say @a[0]; # да, начинается с @
say @a[0, 2]; # срезы тоже работают


Списки создаются с помощью оператора запятая. 1, — это список, а (1) — нет.

Теперь все является объектами, поэтому можно вызывать методы у массивов:

my @b = @a.sort;
@b.elems; # количество элементов
if @b > 2 { say "yes" } # тоже работает
@b.end # номер последнего индекса. Заменяет $#array
my @c = @b.map({$_ * 2 }); # да, map - это тоже метод


Существует сокращенная форма записи старой конструкции цитирования qw(..):

my @methods = <shift unshift push pop end delete sort map>;

хеши

В то время как в Perl 5 хеши — это списки с четным числом элементов, хеши в Perl 6 — списки пар. Пары также используются для других целей, например, для именованных аргументов в функциях, но об этом позже.

Точно так же, как и с массивами, начальный символ (сигил) остается неизменным при индексации массива. А у хешей есть методы, которые можно вызывать.

my %drinks =
France => 'Wine',
Bavaria => 'Beer',
USA => 'Coke';
say "Французы любят ", %drinks{'France'};
%drinks.delete('France');


Обратите внимание, что когда вы обращаетесь к элементам хеша, используя конструкцию %hash{...}, ключи автоматически не квотируются, как в Perl 5. Поэтому %hash{foo} не возвратит значение по ключу foo, а вызовет функцию foo(). Автоматическое квотирование не исчезло, просто изменился синтаксис:

say %drinks<Bavaria>;

напоследок

Многие встроенные методы существуют и как методы, и как функции. Поэтому возможно писать как sort @array, так и @array.sort.

Наконец, следует знать, что [..] и {...} - это всего лишь способ вызова со специальным синтаксисом, а не что-то специфичное для массивов или хешей. Это означает, что они также не привязаны и к конкретному сигилу.

my $a = [1, 2, 3];
say $a[2]; # 3


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

ссылки по теме

http://perlcabal.org/syn/S02.html
http://perlcabal.org/syn/S29.html

урок 2. типы

краткое содержание

my Int $x = 3;
$x = "foo"; # ошибка
say $x.WHAT; # 'Int'

# проверка типа:
if $x ~~ Int {
say '$x содержит в себе Int'
}


В Perl 6 существуют типы. В какой-то степени все является объектом и имеет тип. Переменные могут иметь ограничение типа, но это не обязательно. Вот несколько основных типов, о которых следует знать:

'a string' # Str
2 # Int
3.14 # Num
(1, 2, 3) # List


Все «нормальные» встроенные типы начинаются с прописной буквы. Все «нормальные» типы унаследованы от типа Any и абсолютно все унаследованы от Object.

Возможно ограничить тип значений, которые может хранить переменная, добавив в объявлении имя типа.

my Num $x = 3.4;
my Int @a = 1, 2, 3;


Попытка сохранить в переменной значение "неверного" типа (то есть не указанного типа или подтипа) будет ошибкой.

Тип объявления для массивов используется для их содержимого; так my Str @s является массивом, который может хранить только строки.

интроспекция

Узнать непосредственный тип чего-либо можно, вызвав метод .WHAT.

say "foo".WHAT; # Str

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

if $x ~~ Int {
say 'Переменная $x содержит целое число';
}


причины

Система типов не очень проста, чтобы понять все ее тонкости, но есть ряд весомых причин, объясняющих, зачем нам нужны типы:

Безопасность программирования. Если вы объявили переменную и указали ей конкретный тип, то можете быть уверены в том, что с переменной будет возможно выполнить определенные операции. Нет необходимости делать проверку.

Возможность оптимизации. Когда информация о типе имеется во время компиляции, возможно выполнить определенные оптимизации. В принципе, Perl 6 не обязан быть медленнее, чем C.

Расширяемость. Имея информацию о типах и множественную диспетчеризацию, возможно с легкостью модифицировать работу операторов для определенных типов.

ссылка по теме

http://perlcabal.org/syn/S02.html#Built-In_Data_Types

урок 3. основные управляющие структуры

краткое содержание

if $percent > 100 {
say "странная математика";
}
for 1..3 {
# использование $_ в качестве переменной цикла
say 2 * $_;
}
for 1..3 -> $x {
# использование явной переменной цикла
say 2 * $x;
}
while $stuff.is_wrong {
$stuff.try_to_make_right;
}
die "Доступ запрещен" unless $password eq "Secret";


Большинство управляющих структур Perl 6 совершенно похожи на структуры Perl 5. Самое большое визуальное отличие — теперь нет необходимости писать круглые скобки после if, while, for и так далее.

ветвления

Оператор if преимущественно не изменился, по-прежнему можете использовать блоки elsif и else. Сохранился и unless, только у него нет блока else.

if $sheep == 0 {
say "Какая скука";
} elsif $sheep == 1 {
say "Одна одинокая овца";
} else {
say "Стадо, как прекрасно!";
}


Кроме того, возможно использовать if и unless как модификатор оператора, то есть после инструкции:

say "вы выиграли" if $answer == 42;

циклы

Управление циклами возможно с использованием next и last так же, как и в Perl 5.

Цикл for теперь используется только для итераций над списками. По умолчанию, если явно не указана переменная цикла, используется стандартная переменная $_.

for 1..10 -> $x {
say $x;
}

Конструкция -> $x { ... } по-английски называется “pointy block” («блок со стрелочкой») и похожа на анонимную функцию или на лямба-функцию в Лиспе.

В цикле допустимо использовать более одной переменной:

for 0..5 -> $even, $odd {
say "Чет: $even \t Нечет: $odd";
}


Цикл for также пригоден для итерации над хешами:

my %h = a => 1, b => 2, c => 3;
for %h.kv -> $key, $value {
say "$key: $value";
}


C-подобный-вариант цикла for теперь называется loop:

loop (my $x = 1; $x < 100; $x = $x**2) {
say $x;
}


ссылка по теме

http://perlcabal.org/syn/S04.html#Conditional_statements

урок 4. функции и сигнатуры

краткое содержание

# функция без сигнатуры - так же, как и в perl 5
sub print_arguments {
say "Параметры:";
for @_ {
say "\t$_";
}
}
# Сигнатура с фиксированным количеством параметров и типами:
sub distance(Num $x1, Num $y1, Num $x2, Num $y2) {
return sqrt ($x2-$x1)**2 + ($y2-$y1)**2;
}
say distance(3, 5, 0, 1);
# Параметры по умолчанию
sub logarithm($num, $base = 2.7183) {
return log($num) / log($base)
}
say logarithm(4); # использует значения по умолчания для второго параметра
say logarithm(4, 2); # явный второй параметр
# именованные параметры
sub doit(:$when, :$what) {
say "сделать $what $when";
}
doit(what => 'что-то', when => 'прямо сейчас'); # 'сделать что-то прямо сейчас'
doit(:when<днем>, :what('что-нибудь еще')); # 'сделать что-нибудь еще днем'
# ошибка: doit("что-то", "сейчас")


Функции объявляются с помощью ключевого слова sub и могут иметь список формальных параметров так же, как в C, Java и большинстве других языках программирования. Опционально эти параметры могут быть типизированными.

По умолчанию параметры доступны только для чтения. Но это может быть изменено с помощью так называемых «особенностей» (traits):

sub foo($bar) {
$bar = 2; # запрещено
}
my $x = 2;
sub baz($bar is rw) {
$bar = 0; # разрешено
}
baz($x); say $x; # 0
sub quox($bar is copy){
$bar = 3;
}
quox($x); say $x # 0


Параметры могут быть объявлены необязательными, для этого нужно либо поставить после них знак вопроса ?, либо указать значение по умолчанию.

sub foo($x, $y?) {
if $y.defined {
say "У нас есть $y"
}
}

sub bar($x, $y = 2 * $x) {
. . .
}


именованные параметры

При вызове функции, например, my_sub($first, $second), аргумент $first указывает на первый формальный параметр, $second — на второй и так далее, поэтому они называются «позиционными».

Иногда проще запомнить имена, чем позиции параметров, поэтому в Perl 6 введены именованные параметры:

my $r = Rectangle.new(
x => 100,
y => 200,
height => 23,
width => 42,
color => 'black'
);


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

sub area(:$width, :$height) {
return $width * $height;
}
area(width => 2, height => 3);
area(height => 3, width => 2 ); # то же самое
area(:height<3>, :width<2>); # то же самое


В приведенных выше примерах переменные имеют такие же имена, как и параметры. Однако, возможно использовать и другие имена:

sub area(:width($w), :height($h)){
return $w * $h;
}
area(width => 2, height => 3);

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

sub sqrt($number) { ... };
sqrt(3);
sqrt(number => 3); # тоже работает


«свертывание» (slurp) аргументов

Если просто объявить функцию с параметрами, то это не значит, что необходимо заранее знать число переданных аргументов.

Возможно определить так называемые slurpy-параметры (после всех обычных), которые соберут любые оставшиеся аргументы:

sub tail ($first, *@rest){
say "Первый: $first";
say "Остальные: @rest[]";
}
tail(1, 2, 3, 4); # "Первый: 1\nОстальные: 2 3 4\n"


интерполяция

По умолчанию массивы не являются интерполируемыми в списках аргументов, поэтому в отличие от Perl 5 допустимо написать что-то вроде этого:

sub a($scalar1, @list, $scalar2){
say $scalar2;
}
my @list = "foo", "bar";
a(1, @list, 2); # 2


Это также означает, что по умолчанию невозможно использовать список как список аргументов:

my @indexes = 1, 4;
say "abc".substr(@indexes) # ошибка!


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

say "abcdefgh".substr(|@indexes) # bcde

мультифункции

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

multi sub my_substr($str) { ... } # 1
multi sub my_substr($str, $start) { ... } # 2
multi sub my_substr($str, $start, $end) { ... } # 3
multi sub my_substr($str, $start, $end, $subst) { ... } # 4


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

multi sub frob(Str $s) { say "Это строка $s" }
multi sub frob(Int $i) { say "Это целое число $i" }
frob("x") # Это строка x
frob(2) # Это целое число 2


причины

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

Мультифункции очень полезны, поскольку они позволяют переопределять встроенные функции для новых типов.

Допустим, вы хотите версию Perl 6, которая корректно работает со строками на турецком языке, в которых есть необычные правила для преобразования между строчными и прописными буквами.

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

sub uc(TurkishStr $s) { ... }

Теперь все, что нужно сделать, — это позаботиться о том, чтобы строки имели нужный тип, соответствующий языку, и пользоваться функцией uc как обычно.

Поскольку операторы — тоже функции, такие изменения для определенных типов повлияют и на их работу.

ссылка по теме

http://perlcabal.org/syn/S06.html

урок 5. объекты и классы

краткое содержание

class Shape {
method area { ... } # literal '...'
has $.colour is rw;
}
class Rectangle is Shape {
has Num $.width;
has Num $.height;
method area {
$!width * $!height;
}
}
my $x = Rectangle.new(
width => 30,
height => 20,
colour => 'черный',
);
say $x.area; # 600
say $x.colour; # черный
$x.colour = 'синий';


В Perl 6 используется гораздо более полная объектная модель, чем в Perl 5. Модель содержит ключевые слова для создания классов, ролей, атрибутов, методов и инкапсулированных закрытых атрибутов и методов.

Существует два способа объявления классов:

class ClassName;
# здесь идет описание класса


Первый способ — класс начинается с объявления class ClassName; и простирается до конца файла.

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

class YourClass {
# здесь - описание класса
}
# здесь - другие классы или код


методы

Методы объявляются при помощью ключевого слова method. Внутри метода возможно использовать ключевое слово self для ссылки на объект (инвокант), на котором был вызван метод.

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

Открытые методы могут быть вызваны следующим образом: $object.method при отсутствии аргументов, либо $object.method(@args) или $object.method: @args, если метод принимает параметры.

class SomeClass {
# эти два метода ничего не делают, но возвращают инвокант - экземпляр класса
method foo {
return self;
}
method bar($s: ) {
return $s;
}
}
my SomeClass $x .= new;
$x.foo.bar # то же, что и $x


my SomeClass $x .= new — это, фактически, сокращенная запись my SomeClass $x .= SomeClass.new. Это выражение работает, потому что объявление типа заполняет переменную «прототипом объекта» класса SomeClass, которая является объектным представлением этого типа.

Как и функции, методы могут иметь дополнительные аргументы.

Частные методы могут быть объявлены с помощью my method, а вызваны — записью self!method_name.

class Foo {
my method private($frob) {
return "Частный метод $frob";
}
method public {
say self!private("foo");
}
}


Частные методы не могут быть вызваны извне класса.

атрибуты

Атрибуты объявляются при помощью ключевого слова has и содержат твигил — специальный символ после сигила.

Для закрытых атрибутов — восклицательный знак !, для открытых — точка.
Разница между открытыми и закрытыми атрибутами — только в уровне доступа.

class SomeClass {
has $!a;
has $.b;
has $.c is rw;
method do_stuff {
# можно использовать частные названия взамен открытых
# $!b и $.b - на самом деле, одно и то же
return $!a + $!b + $!c;
}
}
my $x = SomeClass.new;
say $x.a; # ОШИБКА!
say $x.b; # ОК
$x.b = 2; # ОШИБКА!
$x.c = 3; # ОК


наследование

Наследование осуществляется через свойство is.

class Foo is Bar {
# класс Foo унаследован от класса Bar
. . .
}


Применяются все обычные правила наследования — сначала методы ищутся в непосредственном типе, если метод не найден, то поиск повторяется в родительском классе (рекурсивно).

Аналогично, тип порожденного класса совместим с родительским:

class Bar { }
class Foo is Bar { }
my Bar $x = Foo.new();


В этом примере типом переменной $x является Bar, это позволило присвоить объект типа Foo, поскольку «каждый Foo является Bar».

Классы могут наследоваться от нескольких классов:

class ArrayHash is Hash is Array {
. . .
}


роли и композиция

В целом, мир не является иерархичным, соответственно, иногда сложно выстроить все в иерархию наследования.

Это одна из причин, почему в Perl 6 есть роли. Роли весьма похожи на классы, за исключением того, что из них невозможно непосредственно создать объекты. В то время как классы предназначены в первую очередь для согласования типов, роли в первую очередь подразумевают повторное использование кода кода в Perl 6.

role Paintable {
has $.colour is rw;
method paint { ... }
}
class Shape {
method area { ... }
}
class Rectangle is Shape does Paintable {
has $.width;
has $.height;
method area {
$!width * $!height;
}
}


ссылка по теме

http://perlcabal.org/syn/S12.html

урок 6. контексты

краткое содержание

my @a = <a b c>
my $x = @a;
say $x[2]; # c
say (~2).WHAT # Str
say +@a; # 3
if @a < 10 { say "короткий массив"; }


Когда вы пишите что-то вроде этого

$x = @a

в Perl 5, $x содержит меньше информации, чем @a, а именно, только количество элементов в @a.

Чтобы сохранить всю информацию, следует явно взять ссылку: $x = \@a.

В Perl 6 наоборот: по умолчанию ничего не теряется, скаляр просто сохраняет массив.

Это стало возможным благодаря введения обобщенного контекста элемента (называемого в Perl 5 «скаляром») и более специализированным числовым, целочисленным и строковым контекстам. Пустой и списочный контексты остаются неизменными.

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

синтаксис контекст
~stuff строковый
?stuff булевый
+stuff числовой
-stuff числовой (также отрицательный)
$( stuff ) обобщенный контекст элемента
@( stuff ) списочный контекст


контекст срезов

Существует и новый контекст, названный контекстом срезов. Это списковый контекст, в котором списки не интерполируются.

@( <a b> Z <c d> ) # <a c b d>
@@( <a b> Z <c d> ) # (['a', 'c'], ['b', 'd'])


Это было введено после замечания о том, что множество встроенных в список функций имели две версии — одна, которая возвращала плоский список, и другая, которая возвращала список списков.

Возможно принудительно выставить контекст, используя @@( stuff ).

причины

Разработка более специфичных контекстов способно затормозить дизайн языка. Например, кажется преждевременным решать, что должен вернуть список в скалярном контексте — ссылка на список сохранит всю информацию, но это не очень полезно при сравнении чисел. Так что любой возможный выбор кого- нибудь разочарует.

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

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

ссылка по теме

http://perlcabal.org/syn/S02.html#Context

урок 7. правила (ранее — регулярные выражения)

краткое содержание

grammar URL {
token TOP {
<schema> '://'
[<hostname> | <ip> ]
[ ':' <port>]?
'/' <path>?
}
token byte {
(\d**{1..3}) <?{ $0 <= 256 }>
}
token ip {
<byte> [\. <byte> ] ** 3
}
token schema {
\w+
}
token host {
(\w+) ( \. \w+ )+
}
token port {
\d+
}
token path {
<[a..zA..Z0..9-_.!~*'():@&=+$,/]>+
}
}


Регулярные выражения — одна из областей, которая в Perl 6 значительно улучшена и исправлена. Чтобы подчеркнуть этот факт, и поскольку это больше не «регулярные выражения», теперь они называются правилами.

Существуют три существенных изменения и расширения регулярных выражений.

- Очистка синтаксиса.

Много мелких изменений дают возможность создавать правила с легкостью. Например, точка . теперь совпадает с любым символом, старая семантика (все, кроме перевода строки) доступна с помощью \N.
Модификаторы теперь начинаются в начале регулярного выражения, а несохраняющие группы выглядят как [..], что намного легче записать, чем прежнее (?:...).

- Вложенные сохраняющие конструкции и совпадения.

Регулярное выражение вроде (a(b))(c) в Perl 5 при успешном совпадении сохранит ab в $1, b в $2 и c в $3. Это изменилось. Теперь $0 (нумерация начинается с нуля) содержит ab, а $0[0] или $/[0][0] — b. В $1 хранится Cc.

- Именованные правила и грамматики.

Точно так же, как подпрограммы и методы, возможно объявить именованные регулярные выражения. Внутри других регулярных выражений на них можно ссылаться с помощью конструкции . Несколько правил можно объединить в грамматики, которые напоминают классы и поддерживают наследование и композицию.

Эти изменения делают намного упрощают написание и поддержку правил по сравнению с регулярными выражениями Perl 5.

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

очистка синтаксиса

Буквенные символы (то есть, подчеркивание, цифры и все буквы в юникоде) совпадают буквально, и имеют особое значение (в таком случае они называются метасимволами), будучи экранированными обратным слешем. Со всеми остальными символами ровно наоборот — они имеют особое значение, когда неэкранированы.

литерал метасимвол
a b 1 2 \a \b \1 \2
\* \: \. \? * : . ?


Значение есть не у всех метасимволов (пока). Использовать метасимволы с неопределенным значением запрещено.

Вот еще один способ экранировать строку в регулярных выражениях: заключить ее в скобки.

Измененная семантика точки . уже упоминалась, а конструкция [...] обозначает несохраняющюю группу. Символьный класс записывается как <[...]>, а отрицательный — <-[...]>. Метасимволы ^ и $ всегда совпадают с началом и концом текста, для совпадения с началом и концом строки используйте ^^ и $$.

Это означает, что исчезли модификаторы /s и /m. Модификаторы теперь указываются в начале регулярного выражения, и записываются как пары.

if "abc" ~~ m:i/B/ {
say "Совпало";
}


Модификаторы имеют сокращенную и длинную формы. Прежний модификатор C</x> теперь считается включенным по умолчанию, то есть пробелы игнорируются.

сокр. длинный значение
-------------------------------
:i :ignorecase игнорировать регистр (ранее /i)
:a :ignoreaccents игнорировать акценты
:g :global совпасть столько раз, сколько возможно (/g)
:s :sigspace Каждый пробельный символ в регулярном выражении
(необязательно) совпадает с пробелами
:P5 :Perl5 Перейти в режим совместимости с Perl 5
:4x :x(4) Совпасть четыре раза (работает и с другими числами)
:3rd :nth(3) Третье совпадение
:ov :overlap Похоже на :g, но также обрабатывает случаи
с пересечениями
:ex :exhaustive Совпасть всеми возможными способами
:ratchet Не делать откатов


Модификатор :sigspace требует небольшого пояснения. Он заменяет все пробельные символы в шаблоне вызовами <.ws> (то есть вызовами правила ws без сохранения результата). Это правило допустимо переопределить. По умолчанию оно совпадает с одним или более пробельными символами, если оно окружено символами слов, а иначе — с нулем или больше символов. Есть еще несколько модификаторов, но они не так важны, как перечисленные здесь.

объект совпадения

Каждое совпадение создает так называемый объект совпадения, который хранится в специальной переменной $/. Она универсальна. В булевом контексте возвращает Bool::True, если совпадение было удачным.

В строковом контексте возвращается совпавшая строка. При использовании в качестве списка этот список содержит позиционные сохраняющие группы, а в роли хеша — именованные. Методы .from и .to содержат позиции первого и последнего совпавшего символа.

if 'abcdefg' ~~ m/(.(.)) (e | bla ) $<foo> = (.) / {
say $/[0][0]; # c
say $/[0]; # bc
say $/[1]; # e
say $/<foo> # f
}


$0, $1 и так далее — просто ссылки для $/[0], $/[1] и остальных. Аналогично, $/<x> и $/{'x'} ссылаются на $<x>.
Обратите внимание, что все, к чему вы получаете доступ через $/[...] и $/{...}, тоже являются объектом совпадения. Это позволяет с помощью правил строить настоящие деревья разбора.

именованные правила и грамматики

Правилами можно пользоваться либо в старом стиле m/.../, либо объявлять их как методы и подпрограммы.

regex a { ... }
token b { ... }
rule c { ... }


Грамматики могут наследоваться, переопределять правила и так далее.
Различие в том, что token подразумевает модификатор :ratchet (что означает запрет откатов, как группа (?> ... )вокруг каждой части регулярного выражения в Perl 5, а rule — и :ratchet, и :sigspace.

Чтобы вызвать такое правило (будем называть их правилами независимо от того, с помощью какого ключевого слова они объявлены), нужно поместить имя в угловые скобки: <a>. Это неявно привязывает подправило к текущей позиции в строке, и помещает результат в объект совпадения в $/<a>, то есть в именованный объект. Правило также возможно вызвать без сохранения результата, поместив перед ним точку: <.a>.

Грамматика — это группа правил, так же как класс (в качестве примера посмотрите пример в начале урока).

grammar URL::HTTP is URL {
token schema { 'http' }
}


причины

Регулярные выражения Perl 5 часто весьма трудночитаемые, грамматика же побуждает разделять большое регулярное выражение на много намного более читаемых коротких фрагментов. Именованные совпадения делают правила более самодокументируемыми, и многое теперь намного более полноценное, чем было раньше.

Наконец, грамматики настолько мощны, что с их помощью возможно разобрать почти любой язык программирования, включая и сам Perl 6. Это делает грамматику Perl 6 более простой в поддержке и более простой для изменения, чем грамматика Perl 5, которая написана на C и которую невозможно изменить во время разбора.

ссылка по теме

http://perlcabal.org/syn/S05.html

урок 8. объединения

краткое содержание

if $x eq 3|4 {
say '$x либо 3, либо 4'
}
say ((2|3|4)+7).perl # (9|10|11)


Объединения (junctions) — это суперпозиции неупорядоченных значений. Операции над объединениями выполняются независимо для каждого элемента объединения (и даже могут быть распараллелены), а результат собирается в объединение того же типа.

Типы объединений отличаются только когда они встречаются в булевом контексте. Типы такие: any, all, one и none.

Тип Инфиксный оператор
any |
one ^
all &
Запись 1 | 2 | 3 означает то же, что и any(1..3).
my Junction $weekday = any <понедельник вторник среду
четверг пятницу субботу
воскресенье>
if $day eq $weekday {
say "Увидимся в $day";
}


В этом примере оператор eq вызывается для каждой пары ($day, 'понедельник'), ($day, 'вторник') и так далее, а результат помещается вновь в объединение типа any. Как только результат становится известным (в нашем случае после того, как сравнение вернет True), выполнение других сравнений может не выполняться.

Это работает не только с операторами, но и с подпрограммами:


if 2 == sqrt(4 | 9 | 16) {
say "Йоу";
}


Чтобы сделать это возможным, объединения стоят немного особняком в иерархии классов:

Object
/ \
Any Junction
/ | \
Все остальные типы

Чтобы создать подпрограмму, которая принимает объединение и не выполняет автоматическое распараллеливание, необходимо объявить тип параметра либо как Object, либо Junction:

sub dump_yaml(Object $stuff) {
# надеемся, что YAML сможет воспроизвести объединение ;-)
. . .
}


Небольшое предупреждение: объединения могут иногда вести себя по разному в зависимости от того, в чем они участвуют. Например, если обе переменные не являются объединениями, то $a != $b и !($a == $b) всегда означает одно и то же. Если же одна из этих переменных — объединение, то семантика может отличаться:

my Junction $b = 3 | 2;
my $a = 2;
say "Да" if $a != $b ; # Да
say "Да" if !($a == $b); # ничего не напечатается


Выражение 2 != 3 истинно, поэтому так же истинно и $a != 2|3. С другой стороны, сравнение $a == $b возвращает одиночное значение True типа Bool, и его отрицанием будет False.

причины

Перл старается быть довольно близким к естественным языкам, а на естественном языке часто говорят вечно вроде «если результат $такой либо $сякой» вместо того, чтобы говорить «если результат $такой или результат $сякой». Большинство языков программирования позволяют только (перевод) последнего варианта, которые выглядит неуклюже. С помощью объединений Perl 6 разрешает пользоваться и первым вариантом.

Кроме того, это позволяет записывать много сравнений очень просто, иначе пришлось бы использовать циклы.

В качестве примера представьте, что есть массив чисел, и вам хочется знать, являются ли все они неотрицательными. В Perl 5 пришлось бы написать нечто вроде этого:

# Код на Perl 5:
my @items = get_data();
my $all_non_neg = 1;
for (@items){
if ($_ < 0) {
$all_non_neg = 0;
last;
}
}
if ($all_non_neg) { ... }


Или, если вам известен модуль List::MoreUtils:

use List::MoreUtils qw(all);
my @items = get_data;
if (all { $_ >= 0 } @items) { ... }

В Perl 6 это намного короче и аккуратнее:

my @items = get_data();
if all(@items) >= 0 { ... }


ссылка по теме

http://perlcabal.org/syn/S03.html#Junctive_operators

урок 9. сравнения и сопоставления

краткое содержание

"ab" eq "ab" True
"1.0" eq "1" False
"a" == "b" True
"1" == 1.0 True
1 === 1 True
[1, 2] === [1, 2] False
$x = [1, 2];
$x === $x True
$x eqv $x True
[1, 2] eqv [1, 2] True
1.0 eqv 1 False
'abc' ~~ m/a/ True
'abc' ~~ Str True
'abc' ~~ Int False
Str ~~ Any True
Str ~~ Num False
1 ~~ 0..4 True
-3 ~~ 0..4 False


В Perl 6 по-прежнему есть операторы сравнения (eq, lt, gt, le, ge, ne; cmp теперь называется leg), которые рассматривают свои операнды в строковом контексте. Аналогично присутствуют все числовые операторы из Perl 5.

Поскольку объекты — больше чем просто «освященные» ссылки, для их сравнения требуется новый подход. Оператор C<===> для идентичных значений возвращает истину. Для неизменяемых типов, таких как числа и строки, он работает как обычный тест на равенство, для остальных объектов возвращает True только в том случае, если обе переменные относятся к одному и тому же объекту (как сравнение адресов памяти в C++). Оператор eqv проверят два объекта на эквивалентность, то есть являются ли они одного и того же типа и имеют ли одинаковые значения. Две идентично составленные структуры данных признаются эквивалентными.

смартматчинг (smart matching)

В Perl 6 есть оператор, которые может сопоставлять буквально всё со всем, он называется smart match и записывается двумя тильдами ~~. Для неизменяемых типов это просто сравнение эквивалентности. Сопоставление (смартматчинг) с типом проверяет соответствие типу. Сопоставление с регулярным выражением проверяет совпадение с ним. Сопоставление скаляра с объектом Range проверяет, находится ли скаляр в этом интервале. Есть и другие, более мощные формы сопоставления: например, возможно проверить, соответствует ли список аргументов подпрограммы (Capture) списку параметров (Signature), или возможность проверить информацию о файле (как -e в Perl 5).

Что нужно запомнить, так это то, что любой вопрос вида «соответствует ли икс $x игреку $y» может быть выражен через смартматчинг в Perl 6.

ссылка по теме

http://perlcabal.org/syn/S03.html#Nonchaining_binary_precedence

урок 10. контейнеры и значения

краткое содержание

my ($x, $y);
$x := $y;
$y = 4;
say $x; # 4
if $x =:= $y {
say '$x и $y - разные имена одного и того же'
}


Perl 6 делает различие между контейнерами и значениями, которые могут в них храниться.

Обычная скалярная переменная — это контейнер, и может обладать некоторыми свойствами, такими как ограничение типа, ограничения доступа (например, может быть доступ только на чтение), и наконец, может быть ссылкой на другой контейнер.

Размещение значения в контейнере называется присваиванием, а создание ссылки между двумя контейнерами — связыванием.

my @a = 1, 2, 3;
my Int $x = 4;
@a[0] := $x; # теперь @a[0] и $x - одна и та же переменная
@a[0] = 'Foo'; # Ошибка 'Несоответствие типов'


Типы, такие как Int и Str неизменны, то есть объекты этого типа не могут быть изменены; но по-прежнему возможно изменить переменные (точнее, контейнеры), которые содержат эти значения

my $a = 1;
$a = 2; # тут никакого сюрприза


Связывание может быть выполнено также и во время компиляции с помощью оператора ::=.

Возможно проверить, связаны ли два объекта, воспользовавшись оператором сравнения =:=.

причины

Экспорт и импорт процедур, типов и переменных реализован через связывание. Вместо трудно воспринимаемой магии тайпглобов (typeglob) Perl 6 предлагает простой оператор.

ссылка по теме

http://perlcabal.org/syn/S03.html#Item_assignment_precedence

урок 11. изменения в операторах Perl 5

краткое содержание

# битовые операторы
5 +| 3; # 7
6 +^ 3 # 6
5 +& 3; # 1
"b" ~| "d" # 'f'
# конкатенация строк
'a' ~ 'b' # 'ab'
# проверка свойств файла
if '/etc/passwd' ~~ :e { say "exists" }
# повторы
'a' x 3 # 'aaa'
'a' xx 3 # 'a', 'a', 'a'
# тернарный оператор
$a == $b ?? 2 * $a !! $b - $a
# цепочечные сравнения
if 0 <= $angle < 2 * pi { ... }


Все числовые операторы (+, -, /, *, **, %) остались неизменными.

Поскольку |, ^ и & теперь создают объединения, синтаксис битовых операторов изменился. Теперь они содержат префикс данных, например +| является побитовым ИЛИ в числовом контексте, а ~^ — дополнение строки. Аналогично видоизменились операторы битового сдвига.

Конкатенация строк теперь записывается в виде ~, точка же . используется для вызова методов.

Проверка свойств файла теперь записывается с помощью пар (Pair), аналог -e в Perl 5 — :e. Если имя файла хранится не в переменной $_, а где-то в другом месте, то запись выглядит так: $filename ~~ :e.
Оператор повтора x теперь разделен на два: x размножает строки, а xx — списки.

Тернарный оператор, который раньше выглядел как $condition ? $true : $false, теперь записывается иначе: $condition ?? $true !! $false. Операторы сравнения теперь могут быть выстроены в цепочку, так что можно записать $a < $b < $c >, и будет выполнено именно то, что имелось в виду.

причины

Многие изменения в операторах нацелены на то, чтобы более соответствовать алгоритму Хаффмана, то есть дать более частым операциям короткие имена (например, . для вызова методов), а редко используемым оператором — имена подлиннее (например, ~& для побитового И над строками).

Возможность объединения операторов в цепочку — еще один шаг в направлении более естественного языка, и он позволяет записывать выражения, которые часто используются в математике.

ссылка по теме

http://perlcabal.org/syn/S03.html#Changes_to_Perl_5_operators

урок 12. отложенное (ленивое) выполнение (laziness)

краткое содержание

my @integers = 0..*;
for @integers -> $i {
say $i;
last if $i % 17 == 0;
}
my @even = map { 2 * $_ }, 0..*;
my @stuff = gather {
for 0 .. Inf {
take 2 ** $_;
}
}


Perl-программисты обычно ленивы. Так же ленивы и их списки.

В этом случае понятие lazy означает, что выполнение откладывается настолько, насколько возможно. В записи вида @a = map BLOCK, @b блок не выполняется вовсе. Только лишь тогда, когда происходит обращение к элементам из @a, происходит реальное исполнение кода блока и заполнение @a настолько, насколько необходимо.

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

Однако, есть и недостатки: определение длины списка или сортировка отменяет отложенность. Если список бесконечен, то бесконечным скорее всего будет и цикл.

В основном все действия со скаляром (например, List.join) выполняются без отлагательств.

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

При построчном чтении файла в Perl 5 конструкция for (<HANDLE>) не использовалась, поскольку она прочтет весь файл в память, и лишь потом начнет цикл. С отложенным действием такое уместно:

my $file = open '/etc/passwd', :r;
for =$file -> $line {
say $line;
}


Поскольку =$file — итератор над «ленивым» списком (чтобы это в действительности ни означало), строки физически читаются с диска по мере необходимости (разумеется, без учета действий, связанных с буфером).

gather/take

Очень полезная конструкция для создания «ленивых» списков — gather { take }. Она используется следующим образом:

my @list = gather {
while 1 {
# некоторые вычисления;
take $result;
}
}


Оператор gather BLOCK возвращает «ленивый» список. Когда требуются элементы из @list, блок BLOCK выполняется до тех пор, пока не выполнится take. Действие take похоже на возврат, и все собранные с помощью take элементы используются в формировании @list. Когда требуются очередные элементы из @list, выполнение блока продолжается с места после take.

Конструкция gather/take динамически создает область видимости, поэтому допустимо такое:

my @list = gather {
for 1..10 {
do_some_computation($_);
}
}
sub do_some_computation($x) {
take $x * ($x + 1);
}


управление отложенностью

У отложенности есть свои сложности (и когда изучаешь Haskell, замечаешь, насколько необычна система ввода-вывода из-за того, что Haskell содержит одновременно и отложенное выполнение, и не содержит побочных эффектов), а иногда отложенность нежелательна. В этом случае возможно достаточно предварить конструкцию ключевым словом eager.

my @list = eager map { $block_with_side_effects }, @list;

С другой стороны, только списки могут быть «ленивыми» по умолчанию. Но допустимо так же создать и «ленивые» скаляры:

my $ls = lazy { $expansive_computation };

причины

В компьютерной науке многие проблемы можно описать деревом возможных комбинаций, в котором ищется решение. Ключ к эффективному алгоритму — не только в поиске эффективного пути решения, но и в реализации только той части дерева, которая действительно требуется.

С помощью «ленивых» списков возможно рекурсивно определить это дерево и искать в нем, это автоматически создаст только те части дерева, которые реально необходимы.

Вообще, отложенность делает программирование проще, поскольку не требуется знать, потребуется ли вообще результат выполнения, достаточно сделать его «ленивым», и если он не будет использован в вычислениях, то попросту и не выполнится. А если потребуется, то вы ничего не теряете.

ссылка по теме

http://perlcabal.org/syn/S02.html#Lists

урок 13. пользовательские операторы

краткое содержание

multi sub postfix:<!>(Int $x) {
my $factorial = 1;
$factorial *= $_ for 2..$x;
return $factorial;
}
say 5!; # 120


Операторы — это функции с необычным именем и несколькими дополнительными свойствами, такими как приоритет и ассоциативность. Perl 6 следует модели term infix term, где перед term может быть необязательный префиксный оператор, и за ним — постфиксный оператор или скобки.

1 + 1 infix
+1 prefix
$x++ postfix
<a b c> circumfix
@a[1] postcircumfix


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

Расширенное имя оператора включает его тип, за которым после двоеточия следует литеральная строка или список символов, или сами символы. Например: infix:<+> — оператор в выражении 1+2. Другой пример — postcircumfix:<[ ]>, являющийся оператором в @a[0].

Зная это, уже можно определять новые операторы:

multi sub prefix:<€> (Str $x) {
2 * $x;
}
say €4; # 8


приоритет

В выражении типа $a + $b * $c оператор infix:<*> имеет большее высокий приоритет, нежели infix:<+>, поэтому выражение вычисляется как $a + ($b * $c).
Приоритет нового оператора может быть указан относительно существующих:

multi sub infix:<foo> is equiv(&infix:<+>) { ... }
mutli sub infix:<bar> is tighter(&infix:<+>) { ... }
mutli sub infix:<baz> is looser(&infix:<+>) { ... }


ассоциативность

Большинство инфиксных операторов принимают только два аргумента. В выражении типа 1 / 2 / 4 ассоциативность оператора определяет порядок вычисления. Оператор infix:</> левоассоциативен, поэтому выражение интерпретируется как (1 / 2) / 4. Для правоассоциативного оператора, например, infix:<**> (возведение в степень), выражение 2 ** 2 ** 4 будет представлено в виде 2 ** (2 ** 4).

У Perl 6 больше вариантов ассоциативности: non запрещает объединение операторов с одинаковым приоритетом в цепочку (например, недопустимо 2 <=> 3 <=> 4, а оператор infix:<,> имеет списочную (list) ассоциативность. Последовательность 1, 2, 3 транслируется в infix:<,>(1; 2; 3. Наконец, существует цепочечная (chain) ассоциативность: $a < $b < $c преобразуется в ($a < $b) && ($b < $c).

multi sub infix:<foo>
is tighter(&infix:<+>)
is assoc('left')
($a, $b) {
...
}


операторы скобок

Операторы постфиксных скобок (postfcircumfix) — это вызов метода:

class OrderedHash is Hash {
method postcircumfix:<{ }>(Str $key) {
...
}
}


Если он вызван как $object[$stuff], то $stuff будет передан в качестве аргумента методу, а $object станет доступен через self.

Оператор скобок (circumfix) обычно подразумевает иной синтаксис (как в my @list = <a b c>;), и реализуется с помощью макросов:

macro circumfix:«< >»($text) is parsed / <-[>]>+ / {
return $text.comb(rx/\S+/);

}


После свойства is parsed следует регулярное выражение, которое разбирает все, что находится между ограничителями. Если такого правила не указано, то происходит разбор обычного кода Perl 6 (что обычно совсем не то, что вы хотели представить в новом синтаксисе). Вызов Str.comb ищет вхождения, совпадающие с регулярным выражением, и возвращает список текстовых элементов всех совпадений.

перегрузка существующих операторов

Большинство (если не все) существующих операторов являются мультипроцедурами (multi sub) или мультиметодами, и могут поэтому быть настроены для новых типов. Добавление мультипроцедуры и есть перегрузка оператора.

class MyStr { ... }
multi sub infix:<~>(MyStr $this, Str $other) { ... }


Это означает, что возможно написать объекты, которые ведут себя так же как встроенные и «особые» объекты типа Str, Int и так далее.

причины

Разрешая пользователю объявлять новые операторы и перегружать существующие делает определенные пользователем типы такими же мощными и полезными, как и типы встроенные. Если встроенные оказываются недостаточно подходящими, возможно заменить их новыми, которые лучше подходят в данной ситуации, при этом ничего не меняя в компиляторе.

Кроме того, это устраняет разрыв между использованием и изменением языка.

ссылки по теме

http://perlcabal.org/syn/S06.html#Operator_overloading

Если вам интересна техническая подоплека, то есть то, как Perl 6 может реализовать такие замены операторов и другие изменения в грамматике, прочитайте статью http://perlgeek.de/en/article/mutable-grammar-for-perl-6.

урок 14. функция MAIN

краткое содержание

# файл doit.pl
#!/usr/bin/perl6
sub MAIN($path, :$force, :$recursive, :$home = glob("~/")) {
# что-то сделать
}
# из командной строки
$ ./doit.pl --force --home=/home/someoneelse file_to_process


Вызов подпрограмм очень напоминает запуск обычных UNIX-программ из командной строки: в распоряжении есть позиционные, необязательные и именованные аргументы.

Из этого можно получить выгоду, потому что Perl 6 может за вас обработать командную строку, и при вызове передать результат в подпрограмму. Обычно исполняется скрипт (в это время он может изменить аргументы командной строки, хранящиеся в @*ARGS), а затем, если существует, вызывается подпрограмма MAIN.

В том случае, если подпрограмма не может быть вызвана из-за того, что аргументы командной строки не соответствуют формальным параметрам MAIN, печатается автоматически формируемое сообщение об ошибке в параметрах.

Опции командной строки и аргументы подпрограммы соответствуют друг другу следующим образом:

-name :name
-name=value :name<value>
# помните, что <...> похожи на qw(...)
-hackers=Larry,Damian :hackers<Larry Damian>
--good_language :good_language
--good_lang=Perl :good_lang<Perl>
--bad_lang PHP :bad_lang<PHP>
+stuff :!stuff
+stuff=healty :stuff<healthy> but False


Выражение $x = $obj but False означает, что $x является копией $obj, но в булевом контексте возвращает Bool::False.

Так что в простых (и не очень) случаях нет необходимости во внешнем обработчике командной строки, это возможно сделать прямо в функции MAIN.

причины

Причины описанного вполне очевидны: это позволяет делать простые вещи легкими, похожие — похожими, и во многих случаях сокращает обработку командной строки до одной строки кода, а именно до сигнатуры MAIN.

ссылка по теме

Спецификация находится в разделе http://perlcabal.org/syn/S06.html#Declaring_a_MAIN_subroutine.

урок 15. твигилы

краткое содержание

class Foo {
has $.bar;
has $!baz;
}
my @stuff = sort { $^b[1] <=> $^a[1]}, [1, 2], [0, 3], [4, 8];
my $block = { say "Это именованный параметр 'foo': $:foo" };
$block(:foo<bar>);
say "Это файл $?FILE на строке $?LINE"
say "CGI-скрипт" if %*ENV.exists('DOCUMENT_ROOT');


У некоторых переменных есть второй сигил, называемый твигилом (twigil). Он просто означает что переменная необычна и в чем-то отличается, например, может иметь иную область видимости.

Мы уже видели, что открытые и закрытые атрибуты объекта содержат соответственно твигил . и !; это не обычные переменные, они связаны с self. Твигил ^ устраняет особый случай, который был в Perl 5. Чтобы было возможном запись

# внимание: код Perl 5

sort { $a <=> $b } @array


переменные $a и $b являлись особым случаем при обработке с прагмой strict. В Perl 6 существует понятие именованных самообъявленных позиционных параметров, и эти параметры содержат твигил ^. Это означает, что они являются позиционными параметрами относительно текущего блока, ни не участвуют в формировании сигнатуры. Переменные заполняются в лексикографическом (алфавитном) порядке:

my $block = { say "$^c $a $^b" };
$block(1, 2, 3); # 3 1 2


Так что теперь возможно записать так:

@list = sort { $^b <=> $^a }, @list;
# или:
@list = sort { $^foo <=> $^bar }, @list;


И никаких особых случаев.

Чтобы сохранить симметрию между позиционными и именованными аргументами, твигил : делает то же для именованных параметров, поэтому следующие строки примерно эквивалентны:

my $block = { say $:stuff }
my $sub = sub (:$stuff) { say $stuff }


Твигил ? необходим для переменных или констант, которые известны во время компиляции, например номер текущей строки $?LINE (ранее __LINE__), а $?DATA — файловый дескриптор для раздела DATA.

Доступ к глобальным переменным дает твигил *, поэтому %GLOBAL::ENV теперь можно сократить до %*ENV, аргументы командной строки хранятся в @*ARGS, номер текущего процесса — в $*PID и так далее.
Псевдо-твигил < используется в конструкциях типа $<capture>, что является сокращением для переменных $/<capture>, которые обращаются к объекту Match после сопоставления с регулярным выражением.

причины

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

Твигилы пытаются внести некоторый порядок в эти специальные переменные и, с другой стороны, они избавили от наличия особых случаев. В случае с атрибутами объекта запись self.var сокращается до $.var (или, например, @.var).

Как бы там ни было, увеличение «пунктуационного шума» на самом деле намного увеличивает непротиворечивость и надежность программ.

урок 16. перечисления

краткое содержание

enum bit Bool <False True>;
my $value = $arbitrary_value but True;
if $value {
say "Да, истинно"; # напечатается
}
enum Day ('пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс');
if custom_get_date().Day == Day::Sat | Day::Sun {
say "Выходной";
}


Перечисления (enums) универсальны. Они — низкоуровневые классы, состоящие из перечня констант, обычно целых чисел или строк (но это не обязательно).

Эти константы могут действовать как подклассы, методы или обычные значения. Их можно присоединить к объекту с помощью ключевого слова but, которые «подмешает» (mix) перечисление в значение.

my $x = $today but Day::Tue;

Имя типа перечисления также возможно использовать как функцию, и передать ей значение как аргумент:

$x = $today but Day($weekday);

После этого у таких объектов появится метод, имя которого совпадает с типом перечисления, в данном случае Day:

say $x.Day; # 1

Если явно не указать значение в виде пары, то значение первой константы будет 0, следующей — 1 и так далее:

Возможно проверить, «подмешано» ли некоторое значение, с помощью универсального оператора смартматчинга или с вызовом .does:

if $today ~~ Day::Fri {
say "Слава Богу, сегодня пятница"
}
if $today.does(Fri) { ... }


Обратите внимание, что одно имя (например, Fri) возможно использовать только в том случае, если оно однозначно, иначе необходимо записывать полное имя, например Day::Fri.

причины

Перечисления заменяют и «магию», вовлеченную в связанные переменные в Perl 5, и хак return "0 but True" (особый случай, когда появляется предупреждение при использовании в качестве числа). Кроме того, они дают тип Bool.

Перечисления дают мощь и гибкость для добавления любых метаданных при отладке или трассировке.

ссылка по теме

http://perlcabal.org/syn/S12.html#Enums

урок 17. юникод

Поддержка юникода в Perl 5 была имела много слабостей: применялся один и тот же тип для бинарных и текстовых данных. Например, если программа прочитала 512 байт из сетевого сокета, то это однозначно была байтовая строка. Однако, когда (это по-прежнему присутствует в Perl 5) на этой строке вызывается uc, она рассматривается как текст. Рекомендуется сперва декодировать строку, но если подпрограмма получает строку в качестве аргумента, она не может достоверно знать, закодирована оно или нет, то есть должна ли она рассматриваться как байтовая последовательность или как текст.

В свою очередь, Perl 6 предлагает типы buf, который является просто набором байт, и Str — набор логических символов.

Понятие логического символа до сих пор не было четко определено. Если быть более точным, Str — это объект, который может рассматривать на разных уровнях: Byte, Codepoint (кодовая точка — это все то, чему консорциумом по юникоду выделил кодовую позицию), Grapheme (то, что визуально представляет собой символ), и CharLingua (символы, специфичные для языка).

Например, строка с байтами 61 cc 80 (в шестнадцатеричном представлении) состоит из трех байт (это очевидно), но ее также можно рассматривать как состоящую из двух кодовых позиций с именами LATIN SMALL LETTER A (U+0041) и COMBINING GRAVE ACCENT (U+0300), или как графему а.

Поэтому невозможно узнать просто длину строки, вместо этого нужно выбрать одну из трех более определенных длин:

$str.bytes;
$str.codes;
$str.graphs;


Существует также метод chars, возвращающий длину в текущем уровне юникода (который может быть установлен прагмой вроде use bytes, по умолчанию подсчитываются графемы).

В Perl 5 иногда можно было огрести проблем, случайно сконкатенировать байтовую строку с текстовой. Если когда-либо придется столкнуться с этим и в Perl 6, то можно легко выяснить, что случилось, перегрузив оператор конкатенации:

sub GLOBAL::infix:<~> is deep (Str $a, buf $b)|(buf $b, Str $a) {
die "Невозможно добавить к текстовой строке «"
~ $a.encode("UTF-8")
"» последовательность байтов «$b»\n";
}


кодирование и декодирование

Спецификация системы ввода-вывода очень проста и еще не определяет какие-либо уровни кодирования и декодирования, поэтому в этом уроке и нет раздела «Краткое содержание» с примером использования. Я уверен, что такой механизм появится, и я могу предположить, что он будет выглядеть примерно так:

my $handle = open($filename, :r, :encoding<UTF-8>);

регулярные выражения и юникод

Регулярные выражение могут содержать модификаторы, определяющие слой юникода, так что выражение m:codes/./ совпадет в точности с одной кодовой позицией. При отсутствии таких модификаторов будет использоваться текущий уровень.

Символьные классы, например, \w (совпадение с символом слова), ведут себя в соответствии со стандартом юникода. Существуют модификаторы, которые игнорируют регистр (:i) и акценты (:a), и модификаторы для подстановки операторов, которые могут перенести информацию о регистре в подставляемую строку (:samecase и:sameaccent, сокращенно :ii, :aa).

причины

В наше время часто оказывается довольно сложно обеспечить правильную работу со строками. Предположим, есть веб-приложение на Perl 5, и требуется автоматически разбить длинные слова так, чтобы они не портили вывод. Если воспользоваться встроенной функцией subst, то графемы могут случайно потеряться.

Perl 6 будет первым распространенным языком программирования со встроенной поддержкой работы со строками на уровне графем, что позволит избежать множества трудностей с юникодом, и (в сочетании с регулярными выражениями) сделает Perl 6 одним из наиболее мощных языков для обработки строк. Отдельные типы данных для текста и байтовых последовательностей делают весьма простыми отладку и интроспекцию.

ссылка по теме

http://perlcabal.org/syn/S29.html#Str

урок 18. область видимости

краткое содержание

for 1 .. 10 -> $a {
# $a здесь видна
}
# $a здесь не видна
while my $b = get_stuff() {
# $b видна
}
# $b по-прежнему видна
my $c = 5;
{
my $c = $c;
# $c здесь неопределена
}
# $c здесь равна 5
my $y;
my $x = $y + 2 while $y = calc();
# $x по-прежнему видна


лексическая область видимости

Области видимости в Perl 6 весьма похожи на те, что есть в Perl 5. Блок открывает новое лексическую область видимости. Имя переменной сперва ищется в наиболее глубокой лексической области, если оно там не найдено, то затем ищется в следующей области и так далее. Так же как и в Perl 5, my-переменная — переменная лексическая, а объявление our создает лексическую ссылку на переменную пакета.

Но есть и небольшие отличия: переменные, объявленные в блоке, видны до его окончания; видимость переменных, объявленных в заголовке блока (например, в условии цикла while), не ограничиваются этим блоком.

Если необходимо ограничить область видимости, можно воспользоваться формальным параметром блока:

if calc() -> $result {
# здесь доступна переменная $result
}
# здесь переменная $result не видна


Переменные видны сразу после того места, где они объявлены, а не после выражения, как в Perl 5.

my $x = .... ;
# ^^^^^
# Здесь $x видна в Perl 6, но не видна в Perl 5.


динамическая область видимости

Ключевое слово local теперь заменено словом temp, и если за ним не следует инициализация, то используется предыдущее значение этой переменной (а не undef).

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

контекстные переменные

Некоторые переменные, которые глобальны в Perl 5 ($!, $_) являются контекстными переменными в Perl 6, иными словами, они передаются между динамическим областями видимости.

Это решает давнюю проблему в Perl 5. Там подпрограмма DESTROY может быть вызвана около выхода из блока, и случайно изменить значение глобальной переменной, например одной из переменной, содержащих информацию об ошибке:

# Некорректный код Perl 5:
sub DESTROY { eval { 1 }; }
eval {
my $x = bless {};
die "Death\n";
};
print $@ if $@; # Никакого вывода


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

псевдопакеты

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

my $x = 3;
{
my $x = 10;
say $x; # 10
say $OUTER::x; # 3
say OUTER::<$x> # 3
}


Аналогично, функции имеют доступ к переменным, находящимся в вызвавшем их коде, с помощью псевдопакетов CALLER и CONTEXT. Различие заключается в том, что CALLER открывает доступ только к пространству непосредственно вызвавшего кода, а CONTEXT работает как переменные UNIX-оркужения (и должны быть использованы только самим компилятором для обработки $_, $! и тому подобного). Чтобы получить доступ к переменным из окружающей динамической области видимости они должны быть объявлены с помощью is context.

причины

Сейчас общеизвестно, что глобальные переменные на самом деле плохи и причиняют множество проблем. В добавок, у нас есть возможности реализовать лучший механизм областей видимости. Следовательно, глобальными должны быть только те переменные, которые содержат данные, глобальные по своей сути (например, %*ENV или $*PID).

Привила области видимостей блоков значительно упростились.
Вот цитата из документа Perl 5 perlsyn; мы не хотим подобного в Perl 6: «Примечание: поведение модификатора инструкции “my” с модификатором инструкции условной конструкции или цикла (то есть, “my $x if ...”) не определено. Значение “my”-переменной может быть “undef”, любым присвоенным ранее значением или возможно совсем другим. Не полагайтесь на это. Будущие версии Perl могли вести себя иначе по сравнением с той версией, с которой вы работаете сейчас. Это опасно».

ссылки по теме

Области видимости в блоках обсуждаются в документе S04: http://perlcabal.org/syn/S04.html.

В S02 перечислены все псевдопакеты и разъяснена контекстная область видимости:

http://perlcabal.org/syn/S02.html#Names.

урок 19. еще раз о регулярных выражениях

краткое содержание

# обычное сопоставление:
if 'abc' ~~ m/../ {
say $/; # ab
}
# сопоставление с неявным модификатором :sigspace
if 'ab cd ef' ~~ mm/ (..) ** 2 / {
say $2; # cd
}
# подстановка с модификатором :sigspace
my $x = "abc defg";
$x ~~ ss/c d/x y/;
say $x; # abx yefg


Поскольку основы регулярных выражений уже освещены в уроке 7, здесь приведены некоторые полезные (но не слишком структурированные)
дополнительные факты о регулярных выражениях.

сопоставление

Для того, чтобы воспользоваться регулярными выражениями, не требуется писать грамматик, традиционная форма m/.../ по-прежнему работает и обзавелась новым родственником, mm/.../, эта форма подразумевает модификатор :sigspace. Напомню, это означает, что пробельные символы (whitespaces) в регулярном выражении подставляются с помощью правила <.ws>.

По умолчанию правило совпадает с \s+, если пробелы окружены двумя словарными символами (то есть, совпадающих с \w), иначе — \s*.
В подстановках модификатор :samespace заботится о том, чтобы пробельные символы, совпавшие с ws, были сохранены. Аналогично тому, как модификатор :samecase, сокращенно :ii (потому что это вариант :i), сохраняет регистр.

my $x = 'Abcd';
$x ~~ s:ii/^../foo/;
say $x; # Foocd
$x = 'ABC'
$x ~~ s:ii/^../foo/;
say $x # FOO


Это очень полезно, если необходимо глобально переименовать модуль Foo в Bar, но, например, в переменных окружения это записано в верхнем регистре. С помощью модификатора :ii регистр автоматически сохранится.

Информация о регистре копируется символ за символом. Но есть также более сообразительная версия; при наличии модификатора :sigspace (сокращенно :s), делается попытка найти шаблон с информацией о регистре из исходной строки. Распознаются .lc, .uc, .lc.ucfirst, .uc.lcfirst и .lc.capitaliz (Str.capitalize делает прописной первый символ каждого слова). Если такой шаблон найден, он применяется и к строке для замены.

my $x = 'The Quick Brown Fox';
$x ~~ s :s :ii /brown.*/perl 6 developer/;
# в переменной $x теперь 'The Quick Perl 6 Developer'


альтернативы

Альтернативы по-прежнему разделяют с помощью одиночной вертикальной черты |, но это означает не совсем то, что было в Perl 5. Вместо последовательного сопоставления альтернатив и оснановке на первом совпадении, теперь параллельно проверяется совпадение со всеми альтернативами, и отбирается самое длинное.

'aaaa' ~~ m/ a | aaa | aa /;
say $/ # aaa


Хотя это может показаться тривиальной заменой, последствия куда значимее и имеют важное значение для расширяемых грамматик. Поскольку Perl 6 анализируется с помощью грамматики Perl 6, она ответственно за то, чтобы последовательность ++ в конструкции ++$a распозналась как единый токен, а не два токена prefix:<+>.

Прежний, последовательный стиль по-прежнему доступен с помощью ||:

grammar Math::Expression {
token value {
| <number>
| '('
<expression>
[ ')' || { fail("Незакрытая скобка") } ]
}
...
}


Код в фигурных скобках { ... } выполняется как замыкание, и вызов fail в нем приводит к неудаче всего выражения. Гарантируется, что эта ветвь выполнится только в том случае, если предыдущая (в данном случае ')') закончится неудачей, поэтому может быть использована для генерации во время разбора полезного сообщения об ошибке.

Есть и другие способы записать альтернативы, например, при «интерполяции» массива будут проверяться альтернативные совпадения с его значениями:

$_ = '12 oranges';
my @fruits = <apple organge banana kiwi>;
if m:i:s/ (\d+) (@fruits)s? / {
say "You've got $0 $1, I've got { $0 + 2 } of them. You lost.";
}


Существует еще одна конструкция, которая автоматически совпадает с самой длинной альтернативой: множественные регулярные выражения (multi regexes). Их можно записать или с помощью multi tocken name, или с помощью proto:

grammar Perl {
...
proto token sigil { ... }
token sigil:sym<$> { <sym> }
token sigil:sym<@> { <sym> }
token sigil:sym<%> { <sym> }
...
token variable { <sigil> <twigil>? <identifier> }
}


В этом примере показан множественный токен, названный sigil, который параметризован через sym. При использовании сокращенного имени, то есть sigil все эти токены совпадают в альтернативах. Можно об этом думать как об очень неудобном способе записи альтернатив, но это имеет огромное преимущество перед записью '$'|'@'|'%' — это легко расширяется:

grammar AddASigil is Perl {
token sigil:sym<!> { <sym> }
}
# ух ты, у нас есть грамматика Perl 6 с дополнительным сигилом!
Аналогично возможно переопределить существующие альтернативы:
grammar WeirdSigil is Perl {
token sigil:sym<$> { '°' }
}


В этой грамматике сигил для скалярной переменной имеет вид °, так что всегда, когда грамматика ожидает сигила, она ищет символ ° вместо $, но компилятор по-прежнему знает, что это совпало регулярное выражение sigil:sym<$>.

В следующем уроке будет показана разработка реальной, работающей грамматики на примере Rakudo.

урок 20. грамматика для (псевдо-) XML

краткое содержание

grammar XML {
token TOP { ^ <xml> $ };
token xml { <plain> [ <tag> <plain> ]* };
token plain { <-[<>&]>* };
rule tag {
'<'(\w+) <attributes>*
[
| '/>' # одиночный тег
| '>'<xml>'</' $0 '>' # открывающий и закрывающий теги
]
};
token attributes { \w+ '="' <-["<>]>* '"' };
};


Предметом наших уроков до сих пор был язык Perl 6 безотносительно того, какие его части уже реализованы. Чтобы продемонстрировать, что это не только чисто умозрительный язык, и чтобы продемонстрировать мощь грамматик, в этом уроке описано создание грамматики для разбора простого XML, и которая уже сегодня может быть выполнена с помощью Rakudo. Я пользовался ревизией 30633, поэтому чтобы постарайтесь установить именно эту версию, если захотите повторить описанный процесс.

На UNIX-подобных системах это может выглядеть так:

svn co -r 30633 https://svn.perl.org/parrot/trunk parrot
cd parrot
perl Configure.pl && make
cd languages/perl6/ && make perl6


Пользователи Windows на последнем шаге должны собрать perl6.exe.

Теперь запустите make spectest_regression, и если возникнут ошибки, пожалуйста сообщите о них по адресу rakudobug@perl.org.

наша идея XML

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

my @tests = (
[1, 'abc' ], #1
[1, '<a></a>' ], # 2
[1, '..<ab>foo</ab>dd' ], # 3
[1, '<a><b>c</b></a>' ], # 4
[1, '<a href="foo"><b>c</b></a>' ], # 5
[1, '<a empty="" ><b>c</b></a>' ], # 6
[1, '<a><b>c</b><c></c></a>' ], # 7
[0, '<' ], # 8
[0, '<a>b</b>' ], # 9
[0, '<a>b</a' ], # 10
[0, '<a>b</a href="">' ], # 11
[1, '<a/>' ], # 12
[1, '<a />' ], # 13
);
my $count = 1;
for @tests -> $t {
my $s = $t[1];
my $M := $s ~~ XML::TOP;
if !($M xor $t[0]) {
say "ok $count - '$s'";
} else {
say "not ok $count - '$s'";
}
$count++;
}



Это список и «хороших», и «плохих» примеров XML, а небольшой тестовый скрипт выполняет эти тесты, делая сопоставление с XML::TOP. По соглашению, правило, которое проходит грамматику, должно именоваться TOP. Как видите из первого теста, мне не требуем только одного корневого тега, но добавить это ограничение не составит труда.

разработка грамматики

Примеры могут быть загружены с сайта http://perlgeek.de/static/xml-grammar-01.tar.gz.

В XML, конечно же, обязательно должны быть разрешены вложенные теги, поэтому мы сперва посмотрим на второй тест. Поместите это в начало тестового скрипта:

grammar XML {
token TOP { ^ <tag> $ };
token tag {
'<' (\w+) '>'
'</' $0 '>'
};
};


Все токены заканчиваются точкой с запятой. Это не требование в Perl 6, а ограничение Rakudo.

Теперь выполним скрипт:

$ ./perl6 xml-01.pl
not ok 1 - 'abc'
ok 2 - '<a></a>'
not ok 3 - '..<ab>foo</ab>dd'
not ok 4 - '<a><b>c</b></a>'
not ok 5 - '<a href="foo"><b>c</b></a>'
not ok 6 - '<a empty="" ><b>c</b></a>'
not ok 7 - '<a><b>c</b><c></c></a>'
ok 8 - '<'
ok 9 - '<a>b</b>'
ok 10 - '<a>b</a'
ok 11 - '<a>b</a href="">'
not ok 12 - '<a/>'
not ok 13 - '<a />'


Итак, это простое правило разбирает одну пару открывающего и закрывающего тегов, и правильно отвергает все четыре примера невалидного XML. Первый тест должен тоже легко пройтись, поэтому попробуем следующее:

grammar XML {
token TOP { ^ <xml> $ };
token xml { <text> | <tag> };
token text { <-[<>&]>* };
token tag {
'<' (\w+) '>'
'</' $0 '>'
}
};


Напомню, что <-[...]> — это символьный класс, символы которого не должны совпасть.

И запустим:

$ ./perl6 xml-02.pl
Null PMC access in type()
current instr.: 'parrot;XML;xml' pc 975 (EVAL_17:282)
called from Sub 'parrot;XML;TOP' pc 778 (EVAL_17:202)
called from Sub 'parrot;Code;ACCEPTS' pc 5559 (src/gen_builtins.pir:3700)
called from Sub '_block87' pc 1621 (EVAL_17:535)
...


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

Трейс стека показывает, что ошибка произошла в безымянном блоке, но это на самом деле не помогает, потому что мы не знаем, в каком. Фрагмент Code;ACCEPTS указывает на регулярное выражение: смартматчинг ~~ внутри вызывает ACCEPTS, а регулярные выражения компилируются в объекты Code. Он вызывает XML::TOP, затем XML::xml, и затем завершается с ошибкой. Поскольку мы не меняли токен tag, проблему должно быть вызвал text. Немного повозившись, спросив на IRC-канале #perl6 на irc.freenode.org или почитав баг-трекер, выяснилось, что это какое-то странное ограничение, и в Rakudo нельзя называть регулярное выражение именем text.[i] Поэтому переименование text в plain решает проблему, и вот результат:

$ ./perl6 xml-03.pl
ok 1 - 'abc'
not ok 2 - '<a></a>'
(остальное не изменилось)


Почему же теперь перестал работать второй тест, Ответ в том, что Rakudo пока не выбирает наиболее длинный совпадающий токен, и вместо этого перебирает их последовательно. <plain> совпадает с пустой строкой (а, следовательно, и всегда), поэтому <plain> | <tag> никогда не пытается проверить <tag>.

Но мы хотим проверить не просто совпадения либо простого текста, либо тега, а совпадение произвольной комбинации и того, и другого:
token xml { <plain> [ <tag> <plain> ]* };

([...] — это несохраняющая группа, подобная (?: ... ) в Perl 5).
Наконец-то первые оба первые два теста проходят.

В третьем тесте (..<ab>foo</ab>dd) имеется текст между открывающим и закрывающим тегами, поэтому нам потребуется это разрешить. Но между тегами допустим не только текст, но и любой произвольный XML, так что просто вызовем там <xml>:

token tag {
'<' (\w+) '>'
<xml>
'</' $0 '>'
}
./perl6 xml-05.pl
ok 1 - 'abc'
ok 2 - '<a></a>'
ok 3 - '..<ab>foo</ab>dd'
ok 4 - '<a><b>c</b></a>'
not ok 5 - '<a href="foo"><b>c</b></a>'
(остальное не изменилось)


Теперь можно сфокусироваться на атрибутах (типа href="foo"):

token tag {
'<' (\w+) <attribute>* '>'
<xml>
'</' $0 '>'
};
token attribute {
\w+ '="' <-["<>]>* \"
};


Но это не увеличивает число удачных тестов. Причина в пустом пространстве между именем тега и атрибутом. Вместо того, чтобы во многих местах добавлять \s+ или \s*, мы перейдем от token к rule, который подразумевает модификатор :sigspace:

rule tag {
'<'(\w+) <attribute>* '>'
<xml>
'</'$0'>'
};
token attribute {
\w+ '="' <-["<>]>* \"
};


Теперь выполняются все тесты, кроме двух последних:

ok 1 - 'abc'
ok 2 - '<a></a>'
ok 3 - '..<ab>foo</ab>dd'
ok 4 - '<a><b>c</b></a>'
ok 5 - '<a href="foo"><b>c</b></a>'
ok 6 - '<a empty="" ><b>c</b></a>'
ok 7 - '<a><b>c</b><c></c></a>'
ok 8 - '<'
ok 9 - '<a>b</b>'
ok 10 - '<a>b</a'
ok 11 - '<a>b</a href="">'
not ok 12 - '<a/>'
not ok 13 - '<a />'


Они содержат невложенные теги, которые закрыты одним слешем /. Добавить нужный вариант к правилу tag совсем не сложно:

rule tag {
'<'(\w+) <attribute>* [
| '/>'
| '>' <xml> '</'$0'>'
]
};


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

домашнее задание

Попробовать написать грамматику — куда интереснее, чем почитать о том, как она создавалась; вот, например, что вы могли бы воплотить: - Текст содержит сущности вроде &.

- Не знаю, разрешено ли имена XML-тегов начинать с цифры, но текущая грамматика это разрешает. Вы могли бы посмотреть в спецификацию XML, и при необходимости внести в грамматику изменения.

- Текст может содержать блоки <![CDATA[ ... ]]>, в которых XML-подобные теги игнорируются, а символ < не требует экранировки.

- Реальный XML допускает преамбулу, например, <?xml version="0.9" encoding="utf-8"?>, и требует только одного корневого тега, который содержит все остальнео (потребуется изменить несколько тестов).

- Вы могли бы попробовать реализовать форматер для печати XML, который рекурсивно обходил бы по совпадениям в $/. (Это нетривиально; возможно придется обойти некоторые баги в Rakudo, и возможно потребуются другие сохраняющие конструкции.)

Удачного программирования!

причины

Это мощно и интересно.

ссылки по теме

Регулярные выражения описаны в больших подробностях в S05: http://perlcabal.org/syn/S05.html.

Еще больше работающих (!) примеров регулярных выражений и грамматик можно найти в проекте November — вики-движке, написанного на Perl 6. См. http://github.com/viklund/november/ и статью в этом номере журнала.

Уроки опубликованы на сайте автора: http://perlgeek.de/blog-en/perl-5-to-6, там же публикуется и продолжение (на английском языке).



Мориц Ленц (Moritz Lenz, Германия), перевод Анатолия Шарифулина (уроки 1—6) и Андрея Шитова (7—20)
обсудить статью
© сетевые решения
.
.