В 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 миллионов на том же компьютере, но это другая история.)
Давайте посмотрим, «как скачать страницу из интернета» на 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.
Сегодня — заключительная часть рассказа о грамматике для разбора даты. В прошлый раз мы добавили класс с действиями, теперь ради упражнения добавим в этот класс атрибуты (то, что в других языках называют данными класса).
Заодно чуть усложним грамматику и научим ее парсить даты, записанные в виде 28.02.18:
Класс действий теперь должен уметь различать ситуации, когда дата записана с полным годом, а когда год представлен только двумя цифрами.
Дату будем хранить в хеше %date, что немного избыточно для этой задачи, но хорошо иллюстрирует, как действовать, когда акции требуют сохранения промежуточных данных.
Здесь действия должны проявить сообразительность, чтобы правильно оценить, к какому столетию относится год.
Теперь самое важное. Поскольку в классе появился атрибут, компилятору потребуется выделить под него место. Для этого от вас требуется создать объект, а не просто передать имя класса:
my $r = DateStr.parse($t, :actions(DateStrActions.new()));
Вчера мы ввели в грамматику блок кода, выполняемый в ответ на совпавшую подстроку. При увеличении сложности исполняемый код и правила грамматики будут сильно перемешиваться визуально и засорять внешний вид, поэтому есть смысл вынести весь код в отдельный класс:
Методы этого класса должны совпадать с названиями правил и токенов в грамматике. Из вчерашнего примера я убрал код из токена TOP грамматики DateStr и поместил его в метод TOP класса DateStrActions.
Теперь необходимо сообщить о действиях перед парсингом:
my $r = DateStr.parse($t, :actions(DateStrActions));
Все остальное остается неизменным. Программа печатает тот же результат:
Мы уже видели, как сделать грамматику для разбора чисел. Но сами по себе грамматики дают лишь ответ, удовлетворяет ли строка заданным правилам. Обычно еще требуется что-то сделать с имеющимися данными. Для этого к грамматике надо добавить действия (actions).
Рассмотрим простейшее действие на примере грамматики для разбора даты.
Когда вы пробуете печатать объект, например: 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"
На этот раз все получилось. Я скачал последние исходники Rakudo Star и действовал по инструкции:
perl Configure.pl --backend=moar --gen-moar
make
make install
Компилировалось все безумно долго, но при этом Raspberry была доступна и могла показать загрузку процессора. В общей сложности процесс занял часа полтора-два, но часть этого времени — на то, чтобы вспомнить, что у меня там что-то компилируется.
На обычном ноутбуке таких цифр вы не увидите, там все компилируется за минуту-две.
Довольно долгим был и процесс после 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.
Для других ОС
На официальном сайте есть ссылка на гитхаб, где собраны готовые дистрибутивы для многих платформ:
Сегодня мы выйдем за пределы одного чистого программирования и возьмем в руки паяльник. Задача на вечер — помигать светодиодом на Orange Pi.
Orange Pi — это одноплатный мини-компьютер, который, видимо, появился как конкурент Raspberry Pi, при этом и меньше размером, и дешевле. За 15 евро вы получаете четырехядерный компьютер размером со спичечный коробок, куда можно поставить Линукс. Ну не чудо ли? Да еще и c Wi-Fi.
Почти все сказанное ниже применимо, разумеется, и к Raspberry Pi.
С перлом же пришлось повозиться. Я хотел скомпилировать свежий 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.
Здесь я обращаюсь к внешней утилите gpio (из WiringOP): сначала нужно установить один из портов в режим вывода, а затем поочередно устанавливать его в ноль или единицу.
Обратите внимание на интересные моменты в программе.
Во-первых, бесконечный цикл сделан с помощью ленивой последовательности, каждый элемент которой вычисляется как !*, то есть как отрицание предыдущего. В итоге последовательность состоит из бесконечного чередования True и False.
Во-вторых, булевы величины преобразуются в единицу или ноль с помощью префксного оператора: +$_ и затем подставляются в строку, которая выполняется командной оболочкой.
В-третьих, для запуска используется функция shell. Для этой задачи этого более чем достаточно. В идеале нужно было бы написать код, который напрямую устанавливает биты в соответствующих регистрах устройства.
В-четвертых, не забывайте, что sleep в 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.