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.

52. Пара слов о промисах в Perl 6

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

my $promise = Promise.new;

start {
    sleep 3;
    $promise.keep;
}

for 1..* {
    .say;
    sleep 1;    
    last if $promise;
}

Сначала создается объект типа Promise, он сохраняется в одноименной переменной $promise. Затем создается отдельный поток, который через три секунды делает промис (обещание) выполненным:

$promise.keep;

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

last if $promise;

В итоге программа печатает числа от одного до трех:

$ perl6 promise.pl 
1
2
3

После создания промис находится в состоянии Planned. Это одно из возможных состояний. Два других — обещание выполнено или нарушено: Kept или Broken.

Посмотреть текущий статус можно с помощью метода status:

my $promise = Promise.new;
say $promise.status; # Planned

Методы keep и break изменяют состояние промиса, например:

$promise.break; 
say $promise.status; # Broken

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

last if $promise.status eq 'Kept';

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

51. Пример использования кросс-оператора X в Perl 6

Сегодня я покажу пример из реальной практики — как использовать кросс-оператор X для создания списка ключевых слов для контекстной рекламы.

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

my $base = q:to/END/;
    окна
    пластиковые окна
    подоконники
    END

my $tags = q:to/END/;
    установка
    замер
    замер и установка
    изготовление
   END

Цитирующая конструкция q:to// здесь очень уместна — она позволяет не загромождать код кавычками. Теперь делим содержимое на отдельные строки и перемножаем результаты:

my @keywords = $base.lines X~ $tags.lines;

Метаоператор X совместно с оператором конкатенации ~ создает последовательность (Seq) строк, составленных из двух частей. Осталось только напечатать результат:

.say for @keywords;

Вот что получается в итоге:

окна установка
окна замер
окна замер и установка
окна изготовление
пластиковые окна установка
пластиковые окна замер
пластиковые окна замер и установка
пластиковые окна изготовление
подоконники установка
подоконники замер
подоконники замер и установка
подоконники изготовление

Таким же образом можно легко добавить третье измерение (например, географию):

my $location = q:to/END/;

   москва
   московская область
  END

my @keywords = $base.lines X~ $tags.lines X~ $location.lines;
.say for @keywords;

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

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

50. Flip-flop в Perl 6

Ура, это уже 50-я заметка в новом сезоне perl6.ru!

Сегодня мы поговорим об операторе флип-флоп, который похож на то, что было в пятом перле в виде трех точек. В Perl 6 этот оператор имеет вид ff.

Классический пример, на котором видно, как работает оператор, — пропуск Pod-документации:

say 'Hello, World!';

=begin pod
Documentation to the
"Hello, World!" program.
=end pod

say 'All done.';

Теперь берем перл и читаем программу построчно:

my @lines = './helloworld.pl'.IO.lines;

А затем проходимся в цикле по строкам и с помощью ff пропускаем блоки строк, начиная с открывающей строки =begin и заканчивая закрывающей =end:

for @lines {
    next if /^ '=' begin/ ff /^ '=' end/;
    .say;
}

На печати появляется код, очищенный от комментариев:

say 'Hello, World!';
say 'All done.';

49. Как устанавливать модули в Perl 6

В комплект Rakudo Star входит утилита — менеджер модулей zef. (Обратите внимание, что нужен именно Rakudo Star, а не просто компилятор Rakudo Perl 6 — в последнем случае и саму утилиту надо будет установить отдельно.)

Интерфейс довольно простой.

Установка модуля

$ zef install YAML

Далее все происходит автоматически:

===> Searching for: YAML
===> Searching for missing dependencies: TestML
===> Testing: TestML
=== A Basic TestML File ===
=== TestML with no data section ===
=== The Simplest TestML Test That Could Possibly Fail! ===
===> Testing [OK] for TestML
===> Testing: YAML
=== Test Dumping Perl 6 Data Objects to YAML ===
=== Failing Tests For People to Hack On ===
=== Various String Quoting ===
===> Testing [OK] for YAML
===> Installing: TestML
===> Installing: YAML

Удаление модуля

$ zef uninstall YAML

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

Информация о модуле

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

- Info for: YAML
- Identity: YAML
- Recommended By: Zef::Repository::LocalCache
Author:	 Ingy döt Net <ingy@cpan.org>
Description:	 YAML Ain’t Markup Language
License:	 Artistic-2.0
Source-url:	 git://github.com/perl6-community-modules/yaml-pm6.git
Provides: 2 modules
Depends: 1 items

Другие команды

Разумеется, помимо перечисленного, есть еще несколько полезных команд, например, zef search для поиска модуля или zef test для его тестирования. Вызванный без аргументов, zef печатает список команд и опций.

48. Sleep Sort на Perl 6

Да, как же без алгоритма Sleep Sort?! Он должен быть аналогом «Hello, World!» для языков, умеющих делать параллельные вычисления. Итак, Sleep Sort на Perl 6.

my @data = 12, 4, 7, 5, 8, 1, 2;
await gather for @data -> $d {
    take start {
        sleep $d / 10;
        say $d;
    }
}

На каждый элемент данных создается отдельный поток, который ждет $d / 10 секунд и печатает число. Можно попробовать поделить и на большее число, но при этом возможно состояние гонки между потоками.

Сочетание await, gather и take позволяет дождаться все потоки и только потом завершить основную программу.

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

$ perl6 sleep-sort.pl 
1
2
4
5
7
8
12

47. Как отсортировать хеш в Perl 6

Для сортировки хеша в Perl 6 желательно знать, что такое placeholder variable. Как только эта концепция становится понятна, писать условие сортировки становится крайне просто.

Например, создадим хеш с расстояниями до разных городов от Москвы:

my %distance = 
    Владимир    => 185,
    Волгоград   => 1000,
    Калининград => 1227,
    Мурманск    => 1895,
    Новосибирск => 3550;

Теперь задача найти отсортировать элементы по увеличению расстояния.

say %distance.sort({$^a.value <=> $^b.value});

В общем-то, готово. Программа печатает то что надо:

(Владимир => 185 Волгоград => 1000 Калининград => 1227 
Архангельск => 1261 Астрахань => 1411 Мурманск => 1895 
Новосибирск => 3550)

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

Во-первых, переменные $^a и $^b — это плейсхолдеры, которые являются аргументами анонимного блока и сортируются по алфавиту. То есть с тем же успехом их можно было бы назвать $^x и $^y или $^var1 и $^var2.

Во-вторых, внутри этих переменных оказываются объекты типа Pair, который содержит поля key и value:

say %distance.sort({
    say $^a.key ~ ', ' ~ $^a.value ~ ' vs. ' ~ 
        $^b.key ~ ', ' ~ $^b.value;
    $^a.value <=> $^b.value
});

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

Владимир, 185 vs. Новосибирск, 3550
Калининград, 1227 vs. Волгоград, 1000
Мурманск, 1895 vs. Астрахань, 1411
Владимир, 185 vs. Волгоград, 1000
Новосибирск, 3550 vs. Волгоград, 1000
Новосибирск, 3550 vs. Калининград, 1227
Астрахань, 1411 vs. Архангельск, 1261
Владимир, 185 vs. Архангельск, 1261
Волгоград, 1000 vs. Архангельск, 1261
Калининград, 1227 vs. Архангельск, 1261
Новосибирск, 3550 vs. Архангельск, 1261
Новосибирск, 3550 vs. Астрахань, 1411
Новосибирск, 3550 vs. Мурманск, 1895

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

say %distance.sort( -> $город1, $город2 {
    $город1.value <=> $город2.value
});

46. Цитирующие скобки в Perl 6

С помощью угловых цитирующих скобок в Perl 6 очень удобно создавать массивы со строками. В Perl 5 похожий механизм давало слово qw.

my @names = <alpha beta gamma>;
.say for @names;

Вы должны сами определиться с тем, ставить ли пробелы после открывающей и перед закрывающей скобкой. Часто можно видеть вот такой вариант:

my @names = < alpha beta gamma >;

В этом случае в массив попадут ровно те же три элемента.

Perl 6 считает разделителем между отдельными элементами все пробельные символы (whitespace), поэтому данные могут содержать, например, дефисы или точки:

my @nums = < . - 10 20.30 -40 >;

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

my @nums = < . - 10 20.30 -40 >;
for @nums {
    say "{$_.^name} $_";
}

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

Str .
Str -
IntStr 10
RatStr 20.30
IntStr -40

Комментарии (ни обычные, ни embedded) внутри цитирующих скобок не работают. Возьмем программу:

my @data = <
    # comment?
    data_load 
>;
.say for @data;

В массиве окажется три элемента:

#
comment?
data_load

45. Численные методы и Perl 6

Вчера в группе Perl 6 на Фейсбуке Solomon Foster показал вот такой пример:

my @x = FatRat.new(1, 1), 
        -> $x { $x - ($x ** 2 - $N) / (2 * $x) } ... *;

В этом коде в одну строку реализован численный метод вычисления квадратного корня (методом Ньютона).

Давайте возьмем какое-то понятное число $N и посмотрим значение его корня на десятой итерации:

10.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000114617269858815597676188906948935679812072077815172636665039841545242611088386175405939458945274038752495996921868425893616324912131911758516481924280284021811361729487444582731688466773998426538733 . . .

Я показал только часть результата, в реальности цифр еще пять раз по столько же.

Что здесь примечательного?

Во-первых, тип FatRat — это дроби с произвольно большими числителем и знаменателем. Если использовать Rat, то мы довольно быстро придем к обычным числам с плавающей точкой.

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