62. Установка таймаутов в Perl 6

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

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

Вот цикл. Время от времени он выводит счетчик на печать.

for 1 .. * {
    .say if $_ %% 100_000;
}

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

Promise.in(2).then({
    exit;
});

for 1 .. * {
    .say if $_ %% 100_000;
}

Метод Promise.in создает промис, который будет сдержан через заданное число секунд. На этот промис мы — с помощью метода then — добавляем другой промис, который будет сразу выполнен по окончанию первого. Тело промиса — вызов exit.

Запускаем и смотрим:

$ time perl6 timeout.pl
100000
200000
300000
. . .
3700000
3800000
3900000

real 0m2.196s
user 0m2.120s
sys 0m0.068s

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

Для сравнения — программа на Perl 5:

use v5.10;

alarm 2;
$SIG{ALRM} = sub {
    exit;
};

for (my $c = 1; ; $c++) {
    say $c unless $c % 1_000_000;
}

(За две секунды она успевает досчитать до 40 миллионов на том же компьютере, но это другая история.)

61. Метод IO в Perl 6

Работа с файлами в Perl 6 будет очень простой, если знать и пользоваться методом IO, который можно вызывать, например, на строках:

'file.txt'.IO

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

Чтение всего содержимого файла в переменную:

my $content = 'file.txt'.IO.slurp;
say $content;

Прочитать файл построчно и сохранить все строки в массиве:

my @lines = 'file.txt'.IO.lines;
dd @lines; # Array @lines = ["Hello", "World", "", "After empty line", "End"]

Проверка наличия файла:

say 'OK' if 'file.txt'.IO.e;

Является ли путь каталогом?

say 'is dir' if '..'.IO.d;

Выделить путь, название и расширение:

my $f = '/Users/ash/file.txt'.IO;
say $f.dirname;   # /Users/ash
say $f.basename;  # file.txt
say $f.extension; # txt

 

60. Модуль HTTP::UserAgent в Perl 6

Давайте посмотрим, «как скачать страницу из интернета» на Perl 6. Один из удобных способов — воспользоваться модулем HTTP::UserAgent, чей интерфейс очень похож на тот, что был в Perl 5.

Установка модуля с помощью утилиты zef:

$ zef install HTTP::UserAgent

Есть смысл сразу установить поддержку SSL, чтобы иметь возможность ходить на https-адреса:

$ zef install IO::Socket::SSL

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

use HTTP::UserAgent;

sub MAIN($url) {
    my $ua = HTTP::UserAgent.new;
    my $response = $ua.get($url);

    say $response.status-line;
    say $response.content if $response.is-success;
}

Обратите внимание, что автор модуля использует в именах методов дефисы, а не символы подчеркивания: status-line, is-success.

Пробуем:

$ perl6 ua.pl https://perl6.ru/content | less

200 OK
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="profile" href="http://gmpg.org/xfn/11">

<title>Содержание – Вечерний Perl 6</title>

 

59. Действия (actions) в грамматиках, часть 3

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

Заодно чуть усложним грамматику и научим ее парсить даты, записанные в виде 28.02.18:

token TOP {
    | <year> <sep> <month> <sep> <day>
    | <day>  <sep> <month> <sep> <year>
    | <n>    <sep> <month> <sep> <nn>
 }

. . .

token n {
    \d ** 1..2
}
token nn {
    \d ** 2
}

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

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

class DateStrActions {
    has %date;

    method TOP($/) {
        if %date<nn> {
            if %date<nn> > 35 {
                %date<year> = 1900 + %date<nn>;
            }
            else {
                %date<year> = 2000 + %date<nn>;
            }
            %date<day> = %date<n>;
        }
        printf "--> %4i-%02i-%02i\n", 
               %date<year>, %date<month>, %date<day>
    }
    method year($/) {
        %date<year> = ~$/;
    }
    method month($/) {
        %date<month> = ~$/;
    }
    method day($/) {
        %date<day> = ~$/;
    }
    method nn($/) {
        %date<nn> = ~$/;
    }
    method n($/) {
        %date<n> = ~$/;
    }
}

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

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

my $r = DateStr.parse($t, :actions(DateStrActions.new()));

Тестируем код на разных датах:

$ perl6 actions2.pl
2018-02-26 --> 2018-02-26
2019-3-27  --> 2019-03-27
28.04.2020 --> 2020-04-28
30.05.21   --> 2021-05-30
15.06.99   --> 1999-06-15

Полный код вы этого варианта программы можете найти на гитхабе.

58. Действия (actions) в грамматиках Perl 6, часть 2

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

class DateStrActions {
    method TOP($/) {
        printf "--> %4i-%02i-%02i\n", $<year>, $<month>, $<day>
    }
}

Методы этого класса должны совпадать с названиями правил и токенов в грамматике. Из вчерашнего примера я убрал код из токена TOP грамматики DateStr и поместил его в метод TOP класса DateStrActions.

Теперь необходимо сообщить о действиях перед парсингом:

my $r = DateStr.parse($t, :actions(DateStrActions));

Все остальное остается неизменным. Программа печатает тот же результат:

2018-02-26 --> 2018-02-26
2018-02-26 --> 2018-02-26
2018-2-26  --> 2018-02-26
26.02.2018 --> 2018-02-26

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

57. Действия (actions) в грамматиках Perl 6, часть 1

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

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

grammar DateStr {
    token TOP {
        [
        | <year> <sep> <month> <sep> <day>
        | <day>  <sep> <month> <sep> <year>
        ] {
            printf "--> %4i-%02i-%02i\n", 
                   $<year>, $<month>, $<day>
        }
    }
    token year {
        \d ** 4
    }
    token month {
        \d ** 1..2
    }
    token day {
        \d ** 1..2
    }
    token sep {
        <[-./]>
    }
}

my @tests = <
    2018-02-26
    2018-2-26
    26.02.2018
>;

for @tests -> $t {    
    print "$t\t";
    my $r = DateStr.parse($t);
}

Грамматика разрешает один из двух видов формата даты: YYYY-MM-DD или DD-MM-YYYY. Разделителем может быть дефис или точка.

Как только найдено соответствие, Perl 6 выполняет блок кода — в нашем примере он выделен цветом.

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

Внутри действия совпавшие части доступны как элементы объекта $/, например, $<year>.

Программа успешно разбирает все три примера и печатает даты в едином формате:

2018-02-26 --> 2018-02-26
2018-02-26 --> 2018-02-26
2018-2-26  --> 2018-02-26
26.02.2018 --> 2018-02-26

56. Что такое gist в Perl 6

Когда вы пробуете печатать объект, например: say $x, Perl 6 вызывает метод gist. Этот метод определен для всех встроенных типов — где-то он вызывает метод Str, где-то perl, а где-то формирует особое представление.

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

class X {
    has $.value;

    method gist {
        '[' ~ $!value ~ ']'
    }
}

my $x = X.new(value => 42);

say $x; # [42]
$x.say; # [42]

При обращении к say программа печатает число в квадратных скобках: [42].

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

say $x.Str; # X<140586830040512>
say "$x";   # X<140586830040512>

Если вам требуется кастомная интерполяция, переопределяйте и метод Str:

class X {
    has $.value;

    method gist {
        '[' ~ $!value ~ ']'
    }
    method Str {
        '"' ~ $!value ~ '"'
    }
}

my $x = X.new(value => 42);

say $x;     # [42]
$x.say;     # [42]

say $x.Str; # "42"
say "$x";   # "42"

 

55. Perl 6 на Raspberry Pi и другие дистрибутивы

Вчера я пробовал установить Rakudo Perl 6 на Orange Pi. Сегодня пробуем Raspberry Pi (третьей версии).

Raspberry Pi

На этот раз все получилось. Я скачал последние исходники Rakudo Star и действовал по инструкции:

perl Configure.pl --backend=moar --gen-moar
make
make install

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

Вот выдержка из лога:

Stage start      :   0.000
Stage parse      : 767.004
Stage syntaxcheck:   0.000
Stage ast        :   0.000
Stage optimize   : 107.122
Stage mast       : 198.105
Stage mbc        :   8.091

На обычном ноутбуке таких цифр вы не увидите, там все компилируется за минуту-две.

Довольно долгим был и процесс после make install. Видимо, если ставить не Rakudo Star, а просто Rakudo, то все пройдет быстрее, потому что для Rakudo Star устанавливаются и тестируются дополнительные модули.

Но тем не менее, хоп-хоп, запускаем (не дожидаясь окончания действий с модулями):

pi@rpi:~/rakudo-star-2018.01 $ ./perl6 -v
This is Rakudo Star version 2018.01 built on MoarVM version 2018.01
implementing Perl 6.c.

Для других ОС

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

  • Alpine 3.6 x86_64
  • Alpine 3.7 x86_64
  • Centos 7 x86_64
  • Debian 8 amd64
  • Debian 9 amd64
  • Fedora 25 x86_64
  • Fedora 26 x86_64
  • Fedora 27 x86_64
  • openSUSE 42.3 x86_64
  • Ubuntu 14.04 amd64
  • Ubuntu 16.04 amd64
  • Ubuntu 17.10 amd64
  • Ubuntu 16.04 i386
  • Ubuntu 17.10 i386

 

54. Perl 6 на Orange Pi

Сегодня мы выйдем за пределы одного чистого программирования и возьмем в руки паяльник. Задача на вечер — помигать светодиодом на Orange Pi.

Orange Pi — это одноплатный мини-компьютер, который, видимо, появился как конкурент Raspberry Pi, при этом и меньше размером, и дешевле. За 15 евро вы получаете четырехядерный компьютер размером со спичечный коробок, куда можно поставить Линукс. Ну не чудо ли? Да еще и c Wi-Fi.

Почти все сказанное ниже применимо, разумеется, и к Raspberry Pi.

Для экспериментов я установил дистрибутив ARMBIAN Debian 9, Rakudo Perl 6 и WiringOP (ну еще пришлось припаять планку с контактами).

WiringOP — это библиотека и утилиты для работы с портами ввода-вывода. Устанавливается просто:

git clone https://github.com/zhaolei/WiringOP.git -b h3
 
cd WiringOP
chmod +x ./build
sudo ./build

С перлом же пришлось повозиться. Я хотел скомпилировать свежий Rakudo Star и начал делать все по инструкции. Началось все замечательно, но потом намертво встало, нагрев процессор до 60-70 градусов. При этом невозможно было достучаться до компьютера по SSH, хотя видно было, что оно просто дико тормозит, но работает.

В итоге я не дождался, пока все соберется, и пошел искать готовые порты. Нашлись два имени — perl6 и rakudo — и это дистрибутив версии 2016.12.

# apt-get install perl6

Конечно, декабрь 2016 это уже очень старое, но при этом версия языка 6.c, что в целом нормально: эта текущая версия спецификации Perl 6.

This is Rakudo version 2016.12 built on MoarVM version 2016.12
implementing Perl 6.c.

Теперь пишем программу на Perl 6:

my $pin = 7;
shell("gpio mode $pin OUT");

for True, !* ... * {
    shell("gpio write $pin " ~ +$_);
    sleep 0.5;
}

Здесь я обращаюсь к внешней утилите gpio (из WiringOP): сначала нужно установить один из портов в режим вывода, а затем поочередно устанавливать его в ноль или единицу.

Обратите внимание на интересные моменты в программе.

Во-первых, бесконечный цикл сделан с помощью ленивой последовательности, каждый элемент которой вычисляется как !*, то есть как отрицание предыдущего. В итоге последовательность состоит из бесконечного чередования True и False.

Во-вторых, булевы величины преобразуются в единицу или ноль с помощью префксного оператора: +$_ и затем подставляются в строку, которая выполняется командной оболочкой.

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

В-четвертых, не забывайте, что sleep в Perl 6 может принимать и нецелые значения.

53. Функция EVAL в Perl 6

Для выполнения кода, сохраненного в строке, в Perl 6 есть функция EVAL. Именно так, большими буквами, чтобы не было похоже на Perl 5. В работе функции тоже есть отличия от старой eval.

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

Простейший пример:

EVAL('say 123');

Программа ожидаемо печатает 123.

Пример чуть посложнее:

my $var = 42;
EVAL('say $var'); # 42

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

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

my $var = 42;
EVAL("say $var");

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

===SORRY!=== Error while compiling eval3.pl
EVAL is a very dangerous function!!! (use the MONKEY-SEE-NO-EVAL pragma to override this error,
but only if you're VERY sure your data contains no injection attacks)
at eval3.pl:2
------> EVAL("say $var")⏏;

Нужно добавить специальную инструкцию. Либо так:

use MONKEY-SEE-NO-EVAL;

my $var = 42;
EVAL("say $var");

Либо так:

use MONKEY;

my $var = 42;
EVAL("say $var");

Теперь программа, которая будет выполнена внутри EVAL, полностью эквивалентна такой программе:

EVAL('say 42');

Исполнение блоков кода по умолчанию тоже не разрешено. То есть, следующая программа тоже потребует явного упоминания MONKEY:

use MONKEY;

EVAL("say {1 + 2 + 3}"); # 6

Обратите внимание, что EVAL не следует применять вместо try.