25. Альтернативы в регексах Perl 6

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

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

say 'abcd' ~~ / a | ab | abc /;

Программа печатает 「abc」, то есть совпала самая длинная строка, несмотря на то, что она была последней в списке.

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

say 'abcd' ~~ / a || ab || abc /;

На печати окажется 「a」, то есть первый же совпавший вариант.

В Perl 6 к регексам или их частям можно добавить блок кода, который выполнится, если эта часть совпала. Модифицируем предыдущие примеры:

'abcd' ~~ / 
    | a   { say 'a'   }
    | ab  { say 'ab'  }
    | abc { say 'abc' }
/;

'abcd' ~~ /
    || a   { say 'a'   }
    || ab  { say 'ab'  }
    || abc { say 'abc' }
/;

Обратите внимание, что для красоты разрешается ставить еще одну (одинарную или двойную) вертикальную черту перед первой альтернативой. В этом случае пустота перед первой чертой как отдельный вариант не добавится.

Программа печатает две строки:

abc
a

То есть был выполнен только тот блок кода, который соответствует выбранной альтернативе. Во втором примере это очевидно, а в первом — хотя последовательно совпадают и a, и ab, выполняется только третий блок кода.

24. Приблизительное сравнение в Perl 6

В Perl 6 есть оператор приблизительного равенства (approximately-equal operator). Он существует в двух формах — ASCII =~= и юникодном .

Оператор возвращает истину, если относительная разность между операндами меньше величины $*TOLERANCE, которая по умолчанию равна 10–15.

say 3.14159265358979323 =~= pi; # True
say 3.14 =~= pi;                # False

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

{
    my $*TOLERANCE = 0.1;
    say pi =~= 3.14; # True
}

Пара слов о работе оператора. Если оба операнда отличны от нуля, происходит такое сравнение:

|$a - $b| / max(|$a|, |$b|) < $*TOLERANCE

(Здесь |$a| — абсолютное значение величины.)

Если же один из операндов — ноль, то в этом случае возвращается результат сравнения абсолютной величины второго операнда с нулем. То есть в этом случае второй операнд не должен превышать по модулю 10–15:

say 1E-14 =~= 0; # False
say 1E-16 =~= 0; # True

 

23. Встроенные математические константы в Perl 6

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

Во-первых, число пи в двух вариантах: ASCII- и юникодном:

say pi; # 3.14159265358979
say π;

Для каких-то применений, возможно, будет чуть более удобна константа, равная двум пи — tau или τ:

say tau; # 6.28318530717959
say τ;

Наконец, число e, то, которое про экспоненту и логарифмы:

say e; # 2.71828182845905
say 𝑒;

Во второй строке здесь стоит символ U+1D452.

Для обозначения бесконечности можно пользоваться либо Inf, либо :

say (100 ** 100) / ∞; # 0

22. Ввод данных с консоли

В интерактивных программах требуется читать вводимые пользователем строки. В Perl 6 для этого есть специальная функция prompt.

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

my $str = prompt;
say $str;

prompt с аргументом

Функция prompt принимает аргумент — строку, которая будет напечатана перед тем, как начнется ввод:

my $name = prompt('Как вас зовут? > ');
say "Вас зовут $name.";

$*IN.get

Реализация функции prompt очень простая — она вызывает метод get на объекте $*IN, который по умолчанию привязан к стандартному потоку ввода STDIN. Поэтому вместо prompt можно написать более криптографичненько:

my $str = $*IN.get();
say $str;

get

Наконец, есть и просто отдельностоящая функция get, которая делает то же самое:

my $str = get();
say $str;

21. Определение длины строки в Perl 6

Perl 6 сразу и без обиняков считает исходный текст программы сохраненным в UTF-8. Да и вообще, поддержка юникода в Perl 6 очен радует. Сегодня посмотрим, как узнать длину строки.

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

Undeclared routine:
    length used at line 1. Did you mean 'elems', 'chars', 'codes'?

elems, chars и codes

Метод elems относится скорее к массивам, чем к строкам. А вот с chars и codes давайте разберемся.

Метод символов в строке:

say 'hello'.chars;  # 5
say 'café'.chars;   # 4
say 'привет'.chars; # 6
say '嗨'.chars;     # 1

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

Метод codes возвращает число кодовых позиций, необходимых для записи строки. Во многих случаях результат будет совпадать с тем, что возвращает chars. Если же юникодный символ собран из таких частей, которые невозможно воспроизвести в одиночном символе (например, буква плюс какой-то хитрый диакритический знак, который в известных языках не применяется с данной буквой), то Perl не сможет привести этот символ к каноническому виду и, соответственно, chars покажет 1, а codes — 2.

Длина в байтах

Если нужно определить длину строки в байтах, то просто вызвать метод, например, bytes, не получится. Хотя скорее всего вы имели в виду кодировку UTF-8, Perl 6 желает услышать это явно. Действительно, при разном кодировании одна и та же строка занимет разное число байтов. Вот как это делается:

say 'hello'.encode('UTF-8').bytes;  # 5
say 'café'.encode('UTF-8').bytes;   # 5
say 'привет'.encode('UTF-8').bytes; # 12
say '嗨'.encode('UTF-8').bytes;     # 3

В UTF-16, например, число байт будет отличаться:

say 'hello'.encode('UTF-16').bytes;  # 10
say 'café'.encode('UTF-16').bytes;   # 8
say 'привет'.encode('UTF-16').bytes; # 12
say '嗨'.encode('UTF-16').bytes;     # 2

Разумеется, при попытке посчитать байты в Latin-1, часть строк не сможет быть конвертирована:

$ perl6 -e'say "ю".encode("Latin-1")'
Error encoding Latin-1 string: could not encode codepoint 1102
  in block  at -e line 1

20. Функция MAIN в Perl 6, часть 3

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

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

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

sub MAIN(:$value) {
    say "value=$value"
}

Ответ может показаться неожиданным:

$ perl6 main-6.pl 
Use of uninitialized value $value of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
  in sub MAIN at main-6.pl line 1
value=

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

При этом если указать аргумент, отсутствующий в сигнатуре, все работает как надо:

$ perl6 main-6.pl --key=42
Usage:
   main-6.pl [--value=<Any>]

Решение проблемы может быть очень легким: если вы действительно ожидаете параметр, укажите, что он должен быть определен (defined):

sub MAIN(Str:D :$value) {
    say "value=$value"
}

Теперь появляется желанный пример использования:

$ perl6 main-6.pl
Usage:
  main-6.pl [--value=<Str>]

Именованные и позиционные параметры

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

sub MAIN($value, :$key = 'default') { 
   say "$key=$value"
}

В этом случае программа, запущенная без именованного параметра, тоже покажет пример использования, а не ошибку:

$ perl6 main-7.pl 
Usage:
    main-7.pl [--key=<Any>] <value>

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

Так работает:

$ perl6 main-7.pl --key=answer 42 
answer=42

А так нет:

$ perl6 main-7.pl 42 --key=answer 
Usage:
 main-7.pl [--key=<Any>] <value>

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

my %*SUB-MAIN-OPTS = :named-anywhere;

sub MAIN($value, :$key = 'default') { 
    say "$key=$value"
}

Довольно многословно, но как есть. Теперь можно передавать именованные параметры в любом месте командной строки.

Обязательное =

Наконец, следует обратить внимание на то, что значения именованных параметров требуют знака равенства и отсутствие пробелов вокруг него. Запись в командной строке --key answer 42 будет расценена как три аргумента.

А если значение не указано, то оно будет истиной:

$ perl6 main-7.pl --key 42
True=42

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

19. Функция MAIN в Perl 6, часть 2

Вчера мы рассмотрели основные свойства функции MAIN. Сегодня коснемся чуть более сложных деталей.

Начнем с простой программы, похожей на ту, что мы видели вчера:

say 1;

sub MAIN() {
    say 'MAIN'
}

say 2;

Несмотря на то, что инструкции печати расположены и до, и после MAIN, обе они будут выполнены до нее:

$ perl6 main-2.pl 
1
2
MAIN

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

Модификация @*ARGS

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

@*ARGS[0] = '10';

sub MAIN($x, $y) {
    say $x + $y
}

При запуске этой программы $x всегда будет 10:

$ perl6 main-3.pl 3 4
14

Типизированные аргументы

Сигнатура функции позволяет накладывать дополнительные ограничения на тип принимаемых аргументов:

sub MAIN(Int) {
    say 'An integer'
}

Попытка передать строку будет пресечена:

$ perl6 main-4.pl 43
An integer

$ perl6 main-4.pl abc
Usage:
  main-4.pl <Int> 

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

multi sub MAIN(Int) {
    say 'An integer'
}

multi sub MAIN(Str) {
    say 'A string'
}

Со строкой проблем нет, но целое число почему-то не будет принято:

$ perl6 main-5.pl abc
A string

$ perl6 main-5.pl 42
Ambiguous call to 'MAIN'; these signatures all match:
:(Int $)
:(Str $)
  in block <unit> at main-5.pl line 5

Окончание завтра.

18. Функция MAIN в Perl 6

Сегодня мы рассмотрим функцию MAIN. Известно, что в скриптовых языках такой функции обычно нет. В Perl 6 она есть, но не обязательна. Если же она есть, то она будет выполнена 🙂

sub MAIN() {
    say 'Hello, World!';
}

Эта программа вполне себе будет работать, но в данном случае обертывать инструкции в функцию особого смысла нет.

Давайте посмотрим, что произойдет, если добавить какие-то действия вне MAIN:

say 'Hi';

sub MAIN() {
    say 'Hello, World!';
}

Будут напечатаны обе строки:

$ ./perl6 MAIN.pl 
Hi
Hello, World!

Аргументы MAIN

Самое интересное происходит, если этой функции указать аргументы. Начнем с простого:

sub MAIN($x) {
    say $x;
}

Запускаем и видим, что вместо результата (какого?) программа печатает пример использования:

$ perl6 MAIN.pl 
Usage:
  MAIN.pl <x>

Программа ожидает аргумента в командной строке. Этот аргумент будет передан в переменную $x:

$ perl6 MAIN.pl 42
42

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

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

Пример с именованными параметрами:

sub MAIN(:$value, :$power) {
    say $value ** $power; 
}

Эта программа работает так:

$ perl6 MAIN.pl --value=42 --power=3
74088

Пробуем мультифункции:

multi sub MAIN($a) {
    say "[$a]";
}

multi sub MAIN($a, $b) {
    say "[$a and $b]";
}

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

$ perl6 MAIN.pl 42
[42]

$ perl6 MAIN.pl 42 43
[42 and 43]

Интересно, что пример использования в этом случае показывает два варианта:

$ perl6 MAIN.pl 
Usage:
  MAIN.pl <a> 
  MAIN.pl <a> <b>

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