Выпуск 34. Декабрь 2015

Управление модулями и прекомпиляция в Perl 6 | Содержание | Использование Rust из Perl

Perl 6-винегрет

Новогодний оливье — сборная солянка про разные интересные штуки в Perl 6

Юникод

Хранение и обработка символов внутри строк Perl 6 происходит в формате NFG (Normal Form Grapheme). Это название придумано еще в бытность Parrot, а с практической точки зрения достаточно знать, что из любого символа можно получить его NFC-, NFD-, NFKC- и NFKD-формы :-) Это разные канонические и декомпозированные представления символов в юникоде.

Подробности про разные формы представлений с интересными примерами опубликованы на сайте юникода, а история NFG — в блоге Джонатана.

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

say $s.NFC; # codepoint
say $s.NFD;
say $s.NFKC;
say $s.NFKD;

Полное каноническое название возвращает метод .uniname:

say 'й'.uniname; # CYRILLIC SMALL LETTER SHORT I

У строк есть полезный метод .encode, позволяющий получить представление в той или иной юникодной кодировке:

my $name = 'Перл';
say $name.encode('UTF-8');

# utf8:0x<d0 9f d0 b5 d1 80 d0 bb>

(Этот метод не поможет перекодировать CP1215 в КОИ-8.)

Для закрепления материала предлагаю посмотреть на вывод упомянутых методов на разных хитрых символах:

unidump('☭');
unidump('ы');
unidump('å');
unidump('é');
unidump('ϔ'); # один из немногих символов,
              # для которого различаются
              # все канонические представления
unidump('й');
unidump('²');
unidump('Æ');

sub unidump($s) {
    say $s;

    say $s.chars; # число графем
    say $s.NFC;   # code point
    say $s.NFD;
    say $s.NFKC;
    say $s.NFKD;
    say $s.uniname; # юникодное название буквы

    say $s.NFD.list; # списком

    say $s.encode('UTF-8').elems; # сколько байтов
    say $s.encode('UTF-16').elems;

    say $s.encode('UTF-8'); # в виде utf8:0x<...>

    say '';
}

Формы NFKC и NFKD, в частности, могут преобразовать надстрочные и подстрочные индексы в обычные цифры:

say '2'.NFKD; # NFKD:0x<0032>
say '²'.NFKD; # NFKD:0x<0032>

С помощью функции unimatch можно узнать, принадлежит ли символ к той или иной группе символов в юникоде. Например:

say unimatch('ф', 'Cyrillic'); # True

Но нужно быть осторожным, чтобы не разжигать:

say unimatch('ї', 'Cyrillic'); # True
say unimatch('ï', 'Cyrillic'); # False

Здесь были одинаково выглядящие, но разные символы. Их NFD соответственно 0x<0456 0308> и 0x<0069 0308>, а имена — CYRILLIC SMALL LETTER YI и LATIN SMALL LETTER I WITH DIAERESIS.

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

say 1 if 'э' ~~ /<:Cyrillic>/;
say 1 if 'э' ~~ /<:Ll>/; # Letter lowercase

Для создания юникодных строк можно напрямую воспользоваться конструктором класса Uni:

say Uni.new(0x0439).Str;     # й
say Uni.new(0xcf, 0x94).Str; # Ï

Whatever (*)

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

say *.WHAT; # (Whatever)

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

my $f = * ** 2; # возведение в квадрат
say $f(16);     # 256

my $xy = * ** *; # возведение в произвольную степень
say $xy(3, 4);   # 81

Того же результата удастся достичь более традиционным (для старого Perl 6) синтаксисом:

# Анонимный блок кода с одним аргументом $x
my $g = -> $x {$x ** 2};
say $g(16);

# Блок с двумя аргументами, имена сортируются по алфавиту
my $h = {$^a ** $^b};
say $h(3, 4);

Вполне оправдано использование звездочки для создания интервалов (Range):

Пример с циклом:

for (5..*) {
    .say; # построчно печатает от 5 до 10
    last if $_ == 10;
}

Пример с массивом:

my @a = <2 4 6 8 10 12>;
say @a[3..*]; # (8 10 12)

Отдельно следует рассмотреть, что происходит с отрицательными значениями.

Все, начиная с четвертого элемента и заканчивая предпоследним:

say @a[3..*-2]; # (8 10)

Чтобы получить последний элемент, нельзя просто написать @a[-1]:

Unsupported use of a negative -1 subscript to index from the end;
in Perl 6 please use a function such as *-1

Следует добавить звездочку:

say @a[*-1]; # 12

Звездочка подходит и для создания ленивых списков (то есть таких списков, очередной элемент которых вычисляется по мере необходимости):

my @a = (1 ... *); 
for (0..5) {
    say @a[$_];
}

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

my @a = (1, 2 ... *);    # с шагом 1
my @a = (2, 4 ... *);    # четные числа
my @a = (2, 4, 8 ... *); # степени двойки

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

my @fib = 0, 1, * + * ... *; # Фибоначчи

Файлы

Имя файла, который сейчас исполняеся, теперь находится в переменной $*PROGRAM-NAME, а не $*PROGRAM_NAME, как было еще совсем недавно.

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

say slurp $*PROGRAM-NAME;

Противоположная функция — spurt («бить струей»), она записывает данные в файл. Вот пример реализации юниксовой функции cp на Perl 6:

my ($source, $dest) = @*ARGS;

my $data = slurp $source;
spurt $dest, $data;

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

spurt $dest, $data, :append;

Чтобы упасть с ошибкой при попытке записи в существующий файл, добавьте опцию :createonly (:append при этом станет неактуальной):

spurt $dest, $data, :createonly;

И slurp, и spurt принимают аргумент с именем кодировки enc, но опять же, старых кодировок типа 'KOI8' там нет (да и не надо):

my $data = slurp $source, enc => 'UTF-8';
spurt $dest, $data, enc => 'UTF-16';

Если на строке вызвать метод .IO, то возвращается объект класса IO::Path. Метод определен так:

method IO() returns IO::Path:D

(:D здесь не ржачный смайл, а означает defined.)

Например, как получить абсолютный путь к локальному файлу:

say 'file1.pl'.IO.abspath;

Как получить содержимое такого-то каталога:

say '.'.IO.dir;

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

say 1 if 'file.txt'.IO.e; # -e 'file.txt' в Perl 5
say 1 if 'file.txt'.IO.f; # -f
say 1 if '..'.IO.d;       # -d

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

say $filename.IO.extension;

Андрей Шитов


Управление модулями и прекомпиляция в Perl 6 | Содержание | Использование Rust из Perl
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье