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>

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

17. Модификаторы в регексах Perl 6 vs. Perl 5

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

В целом все просто: если раньше буквы типа i ставились после регулярного выражения, то теперь — перед:

Было:

say 'A' =~ /a/i; # 1

Стало:

say 'A' ~~ m:i/a/; # 「A」

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

В Perl 5 было:

say 'A' =~ /(?^i)a/; # 1

В Perl 6 стало:

say 'A' ~~ /:i a/; # 「A」

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

При попытке воспользоваться старым синтаксисом компилятор сообщит об этом на этапе компиляции:

===SORRY!=== Error while compiling /Users/ash/re-5.pl
Unsupported use of =~ to do pattern matching; in Perl 6 please use ~~
at /Users/ash/re-5.pl:3
------> say 'A' =~⏏ /a/i;

16. Создание своих операторов в Perl 6, часть 3

В третьей части мы рассмотрим еще два вида операторов — circumfix и postcircumfix. Начнем с более сложного на вид названия.

Postcircumfix

С операторами такого типа вы все встречались: типичный пример — индексирование массива: @a[$i]. Сам по себе оператор — это пара скобок. Операнды (аргументы) оператора — массив и индекс.

Еще два примера — круглые скобки для вызова функции и угловые скобки для создания массива:

my @a = < a b c >;

Создадим свой:

sub postcircumfix:<¿ ?>(Str $question, Str $answer) {
    say "Q: $question";
    say "A: $answer";
}

"Hello"¿"World"?;

Эта программа напечатает следующее:

Q: Hello
A: World

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

Circumfix

Этот тип оператора еще менее похож на оператор, но тем не менее, с точки зрения языка, это полноправный оператор. Рассмотрим пример с теми же символами:

sub circumfix:<¿ ?>(Str $str) {
    $str.chars
}

say ¿'Hello, World!'?; # 13

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

15. Создание своих операторов в Perl 6, часть 2

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

Постфиксы

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

Типичный пример постфиксного оператора — инкремент ++ или декремент --. Классика кастомного постфиксного оператора — факториал:

sub postfix:<!>(Int $n) {
    [*] 1..$n
}

say 5!; # 120

Инфиксы

Следующий тип — инфиксы. Таких операторов большинство; эти операторы стоят между двумя операндами, например, $a + $b или $a ... $b. Уверен, вы без труда сможете создать собственный оператор при необходимости. Например:

sub infix:<≈>($a, $b) {
    abs($a) - abs($b) < 1
}

say 3.14 ≈ pi; # True

(В Perl 6 уже есть встроенный оператор приблизительного равенства: =~= или , который устроен чуть более сложно.)