Глава 2. Базовый синтаксис

Содержание

Упражнения

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

Представьте, что вы устраиваете турнир по настольному теннису. Рефери сообщают результаты соревнований в формате Player 1 vs Player 2 | 3:2, то есть участник Player 1 выиграл у Player 2 три сета против двух. Для определения победителя создадим скрипт, который просуммирует количество выигранных матчей и сетов для каждого игрока.

Входные данные выглядят следующим образом:

    Beth Ana Charlie Dave
    Ana vs Dave | 3:0
    Charlie vs Beth | 3:1
    Ana vs Beth | 2:3
    Dave vs Charlie | 3:0
    Ana vs Charlie | 3:1
    Beth vs Dave | 0:3

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

Один из способов получить ожидаемый результат с помощью Perl 6 следующий:

    use v6;

    my $file  = open 'scores';
    my @names = $file.get.split(' ');

    my %matches;
    my %sets;

    for $file.lines -> $line {
        my ($pairing, $result) = $line.split(' | ');
        my ($p1, $p2)          = $pairing.split(' vs ');
        my ($r1, $r2)          = $result.split(':');

        %sets{$p1} += $r1;
        %sets{$p2} += $r2;

        if $r1 > $r2 {
            %matches{$p1}++;
        } else {
            %matches{$p2}++;
        }
    }

    my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;

    for @sorted -> $n {
        say "$n has won %matches{$n} matches and %sets{$n} sets";
    }

На экран будет выведен следующий результат:

    Ana has won 2 matches and 8 sets
    Dave has won 2 matches and 6 sets
    Charlie has won 1 matches and 4 sets
    Beth has won 1 matches and 4 sets

Каждая программа на Perl 6 начинается с use v6;. Эта строка указывает компилятору необходимую версию Perl. Благодаря ей, при случайной попытке выполнить файл с помощью Perl 5, появиться полезное сообщение об ошибке.

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

    my $file = open 'scores';

В данной строке my определяет лексическую переменную. Лексическая переменная доступна только в границах текущего блока. Если границы не определены, то видимость распространяется до конца файла. Блок - любая часть кода ограниченная фигурными скобками { }.

Имя переменной начинается с сигила - символа (значка), обладающего по утверждению wikipedia ( и тут я полностью согласен ) определенной магической силой. В Perl 6 к сигилам относятся такие символы, как $, @, % и & ( изредка встречающийся в виде пары двоеточий :: ).

Сигилы наделяют переменную особыми характеристиками, наподобие возможности хранения простого или составного значения. После сигила следует идентификатор, состоящий из букв, цифр и символов подчеркивания. Между буквами возможно использование дефиса - или апострофа ', поэтому isn't и double-click являются допустимыми именами.

Сигил $ указывается перед скалярной переменной. Эти переменные могут хранить одиночное значение.

Встроенная функция open открывает файл с именем scores и возвращает дескриптор файла - объект ассоциированный с указанным файлом. Знак равенства =присваивает дескриптор переменной слева и является способом сохранения дескриптора файла в переменной $file.

'scores' является строковым литералом . Строка является участком текста, в строковый литерал - строкой объявленной непосредственно в программе. В следующей строке строковый литерал указан в качестве аргумента для функции open.

    my @names = $file.get.split(' ');

В данной строке виден правосторонний способ вызова методов - именованного набора команд. Так у хранящегося в переменной $file дескриптора файла вызывается метод get. Метод get читает и возвращает строку из файла, удаляя символ конца строки ( я предполагаю, что это перевод каретки). Далее следует вызов метода split. Он вызывается для строки, возвращаемой get. Эту строку называют инвокантом (invocant). Метод split используется для разбиения строки-инвоканта на части, используя в качестве разделителя шаблон. Шаблон передается через аргумент. В нашем случае в качестве аргумента split получает строку, состоящую из символа пробела.

Таким образом строка из нашего примера 'Beth Ana Charlie Dave' будет преобразована в список небольших строк: 'Beth', 'Ana', 'Charlie', 'Dave'. А затем сохранена (присвоена) в массив @names. Сигил @ маркирует указанную переменную как Array(Массив). Массивы хранят упорядоченные списки.

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

    my %matches;
    my %sets;

Указанные две строки кода определяют два хэша. Сигил % помечает каждую из переменных как Hash (Хэш). Хэш представляет собой неупорядоченный набор пар ключей и значений. В других языках программирования можно встретить другие названия для данного типа: hash table, dictionary или map. Получение из хэш-таблицы значения соответствующего запрашиваемому ключу $key производиться посредством выражения %hash{$key}.

:сноска В отличие от Perl 5, в Perl 6 сигил остается неизменным при обращении к массива или хэшам с использованием [ ] or { }. Данная особенность называется постоянство сигила (sigil invariance).

В программе расчета рейтингов матча, %matches хранит число выигранных матчей каждым игроком. В %sets запоминаются выигранные каждым игроком сеты.

Сигилы указывают на метод доступа к значениям. Переменные с сигилом @ предоставляют доступ к значениям по номеру позиции; переменные с сигилом % - по строковому ключу. Сигил $, обычно, ассоциируется с общим контейнером, которым может содержать что угодно и доступ к данным так же может быть организован любым образом. Это значит, что скаляр может даже содержать составные объекты Array и Hash; сигил $ указывает на тот факт, что данная переменная должна интерпретироваться как одиночное значение, даже в контексте где ожидаются множественные (как например Array и Hash ).

    for $file.lines -> $line {
        ...
    }

Оператор for создает цикл, выполняющий блок кода, ограниченный фигурными скобками содержащий ..., для каждого элемента в списке. Перед каждой итерацией переменная $line устанавливается в очередную строку, прочитанную из файла. $file.lines возвращает список строк из файла scores, начиная со строки, следующей за последней прочитанной $file.get. Чтение продолжается пока ну будет достигнут конец файла.

При первой итерации, $line будет содержать строку Ana vs Dave | 3:0. При второй - Charlie vs Beth | 3:1, и так далее.

    my ($pairing, $result) = $line.split(' | ');

С помощью my можно определить сразу несколько переменных одновременно. В правой части присвоения снова встречаем вызов вызов метода split, но в этот раз в качестве разделителя используется вертикальная черта с пробелами вокруг. Переменная $pairing получает значение первого элемента возвращаемого списка, а $result - второе.

В нашем примере, после обработки первой строки $pairing будет содержать строку Ana vs Dave и $result - 3:0.

Следующие пару строк демонстрируют тот же прием:

    my ($p1, $p2) = $pairing.split(' vs ');
    my ($r1, $r2) = $result.split(':');

В первой строке извлекаются и сохраняются имена двух игроков в переменные $p1 и $p2. В следующей строке примера результаты для каждого игрока сохраняются в переменные $r1 и $r2.

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

Таблица 2.1. Содержимое переменных

ПеременнаяЗначение
$line'Ana vs Dave | 3:0'
$pairing'Ana vs Dave'
$result'3:0'
$p1'Ana'
$p2'Dave'
$r1'3'
$r2'0'

Программа подсчитывает количество выигранных сетов каждым игроком в следующих строках:

    %sets{$p1} += $r1;
    %sets{$p2} += $r2;

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

    %sets{$p1} = %sets{$p1} + $r1;
    %sets{$p2} = %sets{$p2} + $r2;

Выражение += $r1 означает увеличение значения в переменной, расположенной слева, на величину $r1 . Предыдущее значение суммируется с $r1 и результат сохраняется в переменную слева. При выполнении первой итерации %sets{$p1} имеет особое значение и по умолчанию оно равно специальному значению Any. При выполнении арифметических операций Any трактуется как число со значением 0.

Перед указанными выше двух строк кода, хэш %sets пуст. При операциях сложения, отсутствующие ключи в хэше создаются в процессе выполнения, а значения равны 0. Это называется автовивификация (autovivification). При первом выполнении цикла после этих двух строк %sets содержит 'Ana' => 3, 'Dave' => 0. ( Стрелка => разделяет ключ от значения в Паре (Pair).)

    if $r1 > $r2 {
        %matches{$p1}++;
    } else {
        %matches{$p2}++;
    }

Если $r1 имеет большее значение чем $r2, содержимое %matches{$p1} увеличивается на единицу. Если $r1 не больше чем $r2,увеличивается на единицу %matches{$p2}. Также как в случае с +=, если в хэше отсутствовал ключ, он будет атовивифицирован ( это слово приходится даже проговаривать вслух, чтобы написать ) оператором инкремента.

$thing++ - эквивалентен выражениям $thing += 1 или $thing = $thing + 1, и представляет собой более краткую их форму, но с небольшим исключением: он возвращает значение $thing предшествующее увеличению на единицу. Если, как во многих языках программирования, используется ++ как префикс, то возвращается результат, т.е. увеличенное на единицу значение. Так my $x = 1; say ++$x выведет на экран 2.

    my @sorted = @names.sort({ %sets{$_} }).sort({ %matches{$_} }).reverse;

Данная строка содержит три самостоятельных шага. Метод массива sort возвращает отсортированную копию содержимого массива. Однако, по умолчанию сортировка производиться по содержимому. Для нашей задачи необходимо сортировка не по имени игроков, а по их победам. Для указания критерия сортировки методу sort передается блок, который преобразует массив элементов (в данном случае имена игроков) в данные для сортировки. Имена элементов передаются в блок через локальную переменную.

Блоки встречались и ранее: в цикле for использовался -> $line { ... }, а также при сравнении if. Блок - самодостаточный кусок кода Perl 6 с необязательной сигнатурой ( а именно -> $line в примере для for). Подробнее описано в разделе посвященном .

Наиболее простым способом сортировки игроков по достигнутым результатам будет код @names.sort({%matches{$_} }), который сортирует по выигранным матчам. Однако Ana и Dave оба выиграли по два матча. Поэтому, для определения победителей в турнире, требуется анализ дополнительного критерия - количества выигранных сетов.

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

После первой сортировки имена игроков располагаются в следующем порядке: Beth Charlie Dave Ana. После второго шага данный порядок сохраняется. Связано с тем, что количество выигранных сетов связаны в той же последовательности, что и числовой ряд выигранных матчей. Однако, при проведении больших турниров возможны исключения.

sort производит сортировку в восходящем порядке, от наименьшего к большему. В случае подготовки списка победителей необходим обратный порядок. Вот почему производится вызов метода .reverse после второй сортировки. Затем список результатов сохраняется в @sorted.

    for @sorted -> $n {
        say "$n has won %matches{$n} matches and %sets{$n} sets";
    }

Для вывода результатов турнира, используется цикл по массиву @sorted, на каждом шаге которого имя очередного игрока сохраняется в переменную $n. Данный код можно прочитать следующим образом: "Для каждого элемента списка sorted: установить значение переменной $n равное текущему элементу списка, а затем выполнить блок". Команда say выводит аргументы на устройство вывода (обычно это - экран) и завершает вывод переводом курсора на новую строку. Чтобы вывести на экран без перевода курсора в конце строки, используется оператор print .

В процессе работы программы, на экране выводится не совсем точная копия строки, указанной в параметрах say. Вместо $n выводится содержимое переменной $n - имена игроков. Данная автоматическая подстановка значения переменой вместо ее имени называется интерполяцией. Интерполяция производится в строках, заключенных в двойные кавычки "...". А в строках с одинарными кавычками '...' - нет.

    my $names = 'things';
    say 'Do not call me $names'; # Do not call me $names
    say "Do not call me $names"; # Do not call me things

В заключенных в двойные кавычки строках Perl 6 может интерполировать не только переменные с сигилом $, но и блоки кода в фигурных скобках. Поскольку любой код Perl может быть указан в фигурных скобках, это делает возможным интерполировать переменные с типами Array и Hash. Достаточно указать необходимую переменную внутри фигурных скобок.

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

    say "Math: { 1 + 2 }"                   # Math: 3
    my @people = <Luke Matthew Mark>;
    say "The synoptics are: {@people}"      # The synoptics are: Luke Matthew Mark

    say "{%sets}";                          # From the table tennis tournament

    # Charlie 4
    # Dave    6
    # Ana     8
    # Beth    4

Когда переменные с типом массив или хэш встречаются непосредственно в строке, заключенной в двойные кавычки, но не в внутри фигурных скобок, они интерполируются, если после имени переменной находится postcircumfix - скобочная пара следующая за утверждением. Примером может служить обращение к элементу массива: @myarray[1]. Интерполяция производится также, если между переменной и postcircumfix находятся вызовы методов.

    my @flavours = <vanilla peach>;

    say "we have @flavours";           # we have @flavours
    say "we have @flavours[0]";        # we have vanilla
    # so-called "Zen slice"
    say "we have @flavours[]";         # we have vanilla peach

    # method calls ending in postcircumfix
    say "we have @flavours.sort()";    # we have peach vanilla

    # chained method calls:
    say "we have @flavours.sort.join(', ')";
                                    # we have peach, vanilla