Итальянский Perl-воркшоп

22 и 23 октября в Пизе пройдет пятый итальянский Perl-воркшоп.

Итальянский воркшоп (как и недавний Nordic Perl Workshop в Осло) собрал участников не только из Италии. В списке числятся 13 стран: Италия, Великобритания, Германия, Ирландия, Испания, Норвегия, Россия, Голландия, Иран(!), Бельния, США, Австрия, Словакия.

Изменения в работе given/when в Perl 5.10.1 (часть 2)

Второе изменение в работе when — возможность использования оператора //.

Оператор defined-or (//), появившийся в Perl 5.10, теперь может быть использован и как булево выражение в операторе when.

Работает он так, как интуитивно и ожидается:

use v5.10.1;

my $approx1 = undef;
my $approx2 = 3;
my $approx3 = 3.14;

given (3) {
    when($approx1 // $approx2) {say '1 or 2'}
    when($approx2 // $approx3) {say '2 or 3'}
    default {say 'None'}
}

В этом примере сработает первое условие.

Наиболее очевидное применение оператора // — подстановка значения по умолчанию. В следующем примере показана функция, которая ищет либо явно указанное число, либо 123 по умолчанию:

use v5.10.1;

my @data = <DATA>;

find(\@data);
find(\@data, 456);

sub find {
    my $a_ref = shift;
    my $value = shift;   

    say "find called";

    for (@$a_ref) {
        when ($value // 123) {say "Number $_"}
        when (/^\w+$/) {say "String $_"}
    }
}

__DATA__
alpha
123
beta
456
gamma
7.89

Поиск адекватных примеры из реальных приложений, в которых применяются новшества, по-видимому потребует не один месяц ожидания 🙂

Изменения в работе given/when в Perl 5.10.1 (часть 1)

В недавно вышедшем релизе Perl 5.10.1 слегка изменилась работа оператора when.

В частности, оператор when теперь понимает конструкцию «флип-флоп».

«Флип-флоп» — это оператор диапазона .. в булевом контексте. В документации приводится пример выражения для поиска POD-комментариев:

when (/^=begin/ .. /^=end/) {
   # do something
}

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

use v5.10.1;

my $lineno = 0;
for (<DATA>) {
    chomp;
    print 'Line ', ++$lineno, ': ';
    when (/^\.HLL/) {say "switching language to $_"}
    when (/^\.sub/ .. /^\.end/) {say "subroutine body: $_"}
}

__DATA__
.HLL unknown
.sub main :main
    say "Demonstrating inc"
    $I = 2
    inc $I
    say $I
.end

Эта программа напечатает комментарии к каждой строке кода, объясняя его назначение:

Line 1: switching language to .HLL unknown
Line 2: subroutine body: .sub main :main
Line 3: subroutine body:     say "Demonstrating inc"
Line 4: subroutine body:     $I = 2
Line 5: subroutine body:     inc $I
Line 6: subroutine body:     say $I
Line 7: subroutine body: .end

Cледует обратить внимание на два момента. Во-первых, выражение, переданное when, срабатывает на всех строках подпрограммы, включая первую и последнюю, заданные границами /\^.sub/ и /\^.end/.

Во-вторых, оператор ведет себя «нежадно», что позволяет находить непересекающиеся последовательности. Вот программа, в которую дописана дополнительная подпрограмма на PIR, и строка when, находящая пустую строку:

use v5.10.1;

my $lineno = 0;
for (<DATA>) {
    chomp;
    print 'Line ', ++$lineno, ': ';
    when (/^\.HLL/) {say "switching language to $_"}
    when (/^\.sub/ .. /^\.end/) {say "subroutine body: $_"}
    when (/^$/) {say "empty line"}
}

__DATA__
.HLL unknown
.sub main :main
    say "Demonstrating inc"
    $I = 2
    inc $I
    say $I
.end

.sub another
    say 'Hey, you!'
.end

Вывод программы подтверждает правильность ее работы:

Line 1: switching language to .HLL unknown
Line 2: subroutine body: .sub main :main
Line 3: subroutine body:     say "Demonstrating inc"
Line 4: subroutine body:     $I = 2
Line 5: subroutine body:     inc $I
Line 6: subroutine body:     say $I
Line 7: subroutine body: .end
Line 8: empty line
Line 9: subroutine body: .sub another
Line 10: subroutine body:     say 'Hey, you!'
Line 11: subroutine body: .end

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

use v5.10.1;

my @programming_languages = qw(
    Pascal Pawn PCASTL PCF PEARL Perl PHP Phrogram Pico Pict Piet Pike PIKT PILOT Pizza PL/0 PL/B PL/C PL/I PL/M PL/P PL/SQL PL360 PLANC Plankalkül PLEX PLEXIL Pliant POP-11 Poplog PostScript PortablE Powerhouse PPL Processing Prograph PROIV Prolog Promela PROTEL Proteus ProvideX Pure Python
);

for (@programming_languages) {
    when (/^pa/i .. /^pi/i) {push @page, $_}
}
pop @page;
say join ', ', @page;

Программа подготовит данные для страницы словаря PA—PI:

Pascal, Pawn, PCASTL, PCF, PEARL, Perl, PHP, Phrogram

Разумеется, в последнем примере возможно было воспользоваться одним регулярным выражением:

my @page = ();
for (@programming_languages) {
    when (/^p[a-h]/i) {push @page, $_}
}

say join ', ', @page;

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

say join ', ', select_range('pa', 'pi');

sub select_range {
    my ($from, $to) = @_;

    my @page = ();
    for (@programming_languages) {
        when (/^$from/i .. /^$to/i) {push @page, $_}
    }
    pop @page;
   
    return @page;
}

TinyURL::RU

Мой коллега подготовил и опубликовал модуль TinyURL::RU для работы с сайтом byst.ro (он же — tinyurl.ru).

Интерфейс модуля предельно прост и содержит две функции: shorten для сокращения адреса и lenghen для обратного преобразования:

use TinyURL::RU qw(shorten lengthen);

say shorten("http://ironman.enlightenedperl.org/");
say lengthen("77d");

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

my $tower = shorten("http://maps.google.com/maps. . .", "pisa", "tower");

Этот пример создаст адрес pisa.byst.ro/tower (если он, конечно, был доступен на момент запроса).

Модуль содержит плагин WWW::Shorten::TinyURL::RU, который может работать вместе с WWW::Shorten Дейва Кросса.

Пересмотр WWW::Page

Я использую подход, заложенный в модуле WWW::Page, уже несколько лет, постоянно изменяя его код. Не будет преувеличением утверждение, что на каждом сайте работают разные варианты модуля.

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

Сейчас URI запрошенной страницы отображается на один из нескольких XML-файлов с описаниями страниц. Иными словами, некий диспатчер определяет тип страницы и смотрит его описание в соответствующем файле. В таких XML, в частности, подключается один из кастомных модулей, отвечающих за логику приложения, дана ссылка на файл с XSLT-преобразованиями и записаны вызовы функций (из кастомного модуля). Типичный пример подобного файла описания входит в поставку.

Со временем стало ясно, что такая структура, хотя и позволяет полную свободу, отнимает много времени при добавлении на сайт новых страниц (точнее, типов страниц). Дело даже не в том, что это отнимает время; дело в том, что каждая такая операция — типовая: обновить диспатчер, научив его разпознавать новые адреса, создать описание страницы в XML, создать файл с XSLT.После выхода Perl 5.10 в диспатчерах применяется конструкция given/when, и на каждый тип страниц обычно приходится одна строка:

given($uri) {
    when(m{^/?$})
       {$path = 'index.xml'}
    when(m{^/api/(rate)/[^/]+/?$})
       {$path = "api/$1.xml"}
    when(m{^/api/[^/]+/?$})
       {$path = 'api/rates-all.xml'}
    when(m{^/(currency|list|cloud)/?$})
       {$path = "$1.xml"}
 ...

Не слишком утомительно и вполне расширяемо. Однако задача такого распределителя — подготовить переменную $path, записав в нее имя соответствующего XML-файла с описанием страницы.

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

Сейчас (на самом деле лет уже 10 как, еще до появления WWW::Page) я вижу такое развитие подхода. Вместо имен файлов диспатчер сразу будет возвращать объекты страниц соответствющих классов. Есть, например, страница «Новости», для нее возвращается MySite::Page::News.

given($uri) {
    when(/^news$/)
       {return new MySite::Page::News::List}
    when(/^news/(.*)$)
       {return new MySite::Page::News::Detail($1)}

Этот подход я опробую на сайте books.perl.org.

Хитрый момент во множественном наследовании

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

Простой пример. Имеется два модуля BaseA и BaseB, содержащие только метод import.

package BaseA;

sub import {
    print "Base A\n";
}

1;


package BaseB;

sub import {
    print "Base B\n";
}

1;

Модуль Derived содержит лишь инструкции, указывающие эти два класса как базовые:

package Derived;

use base BaseA;
use base BaseB;

1;

Что произойдет, если в программе используется модуль Derived, и вызван метод import?

use Derived;

Derived->import();

На печать дважды выводится строка Base A (первый раз при подключении, второй — при явном вызове import).

А вот если поменять порядок наследования внутри Derived:

package Derived;

use base BaseB;
use base BaseA;

1;

то на печать дважды выводится Base B.

Perl, Пърл, पर्ल

Мне стало интересно, как выглядит название языка Perl на языках, не использущих латинский алфавит.

На википедии нашлось более десятка вариантов названия:

Арабский — يرلبيرل
Болгарский — Пърл
Грузинский — პერლი
Гуджарати — પર્લ
Иврит — פרל
Корейский — 펄
Персидский — پەرل
Сербский — Перл
Тайский — เพิร์ล
Тамильский — பெர்ள்
Фарси — پرل
Хинди — पर्ल
Японский — パール

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

Планы на 2010 материализуются

В списке сайтов конференций, построенных на платформе Act, появилось первое мероприятие на 2010 год.

Это второй болгарский Perl-воркшоп «BG Perl — 2», который состоится в Софии в конце января или начале февраля.

Предыдущий воркшоп 31 января 2009 года собрал около 40 человек, и помещение оказалось немного тесным. Как организатор могу сказать, что это мероприятие я считаю одним из лучших, которые мы сделали. Кстати, в очередной раз замечу, что участники весьма хорошо общались по-английски. Все выступления, за исключением вступительного слова, были на английском языке.

Недавно к соревнованию Iron Man присоединился один из участников воркшопа, Петар Шангов, который открыл блог mechanicalrevolution.com. (На фотографии видны сложенные руки Петара — он крайний справа за передним рядом столов.)

Верблюды

В рассылке Лондон.pm активно обсуждаются плюшевые верблюды, их стоимость и где приобрести.

Где бы вы ни встретили Лиона Брокарда (на фото справа), всегда увидете с ним маленького плюшевого верблюда, которого Лион постоянно привозит на все мероприятия.

Верблюда зовут Амелия и он является официальным верблюдом London.pm.

Обсуждение, впрочем, перешло от ссылок на Амазон к проблеме отстрела верблюдов в Австралии, а затем и к блюдам из верблюжатины.

Inline::CPP

Inline::CPP — замечательный инструмент для того, чтобы с легкостью собирать вместе компоненты, написанные на C++ и Perl.

Не то, чтобы мне потребовалось радикально оптимизировать программу, переписав критические куски на C++. Мне просто захотелось сделать на этом языке часть проекта, написанного на Perl. Мне потребовался грамматический парсер, который легко удалось написать, используя библиотеку Spirit из комплекта Boost.

Что у нас есть для вызова функции из программы на Perl? У нас есть XS, но он в оригинале пригоден для связки с C, а не C++. Даунгрейдить программу ради промежуточного инструмента — не выход. В любом случае писать приходится на синтетическом языке (я пробовал).

Существуют несколько рецептов того, как используя XS подключить фрагменты программы на C++. Но даже те методы, которые аккуратно и доступно изложены, неадекватно сложны для вызова calc(«2+2»). Например, по одному из старых рецептов требуется выполнить с десяток шагов, найти специальный файл perlobject.map, а в каталоге с проектом возникает месиво из файлов:

blib        lib          MANIFEST      MyPackage.o    perlobject.map    ppport.h  typemap
Changes     Makefile     MyPackage.bs  MyPackage.xs   perl-xs-c++.html  README
_howto.txt  Makefile.PL  MyPackage.c   MyPackage.xsc  pm_to_blib        t

Мой выбор — Inline::CPP. Вся программа сводится к подключению этого модуля и вызову нужной функции:

use Modern::Perl;
use Inline 'CPP' => "./calc.cpp";

say calc($ARGV[0]);

Магия компиляции и сборки остается за горизонтом; единственное видимое проявление — каталог _Inline в рабочей директории (но и его можно перенести в /tmp или куда угодно). И, разумеется, замедление первой загрузки программы. При повторных запусках задержек нет, потому что используются готовые .so-библиотеки.

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

#include<iostream>

using namespace std;
double calc(char*);

int main() {
    cout << calc("1+2*3") << "\n";
}

У Inline::CPP пара мелких недостатков (которые, в прочем, никак не омрачают впечатление, удобство и пользу модуля). Во-первых, вывод сообщений об ошибках, который в моем случае по умолчанию попадал в логи Апача, оказывается месивом из строк и литерализированных переводов строк. Вместе с сообщением о самой ошибке (а в C++, когда используешь шаблонные классы, легко получить многостраничное сообщение даже при небольшой ошибке) сюда же попадает командная строка, с которой вызывался компилятор C++:

/usr/bin/perl /usr/share/perl/5.10/ExtUtils/xsubpp  -typemap /usr/share/perl/5.10/ExtUtils/typemap   Calculator_f4e3.xs > Calculator_f4e3.xsc && mv Calculator_f4e3.xsc Calculator_f4e3.c\ng++ -c   -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g   -DVERSION=\\"0.00\\" -DXS_VERSION=\\"0.00\\" -fPIC "-I/usr/lib/perl/5.10/CORE"   Calculator_f4e3.c\nIn file included from Calculator_f4e3.xs:15:\n/usr/include/boost/spirit/core.hpp:18:4: warning: #warning "This header is deprecated. Please use: boost/spirit/include/classic_core.hpp"\nCalculator_f4e3.xs: In function 'double eval_expr()':\nCalculator_f4e3.xs:154: error: 'info' was not declared in this scope\nCalculator_f4e3.xs: In function 'double calc(const char*)':\nCalculator_f4e3.xs:190: error: 'exal_expr' was not declared in this scope\nCalculator_f4e3.c: In function 'void boot_WHL__Parser__Calculator_f4e3(PerlInterpreter*, CV*)':\nCalculator_f4e3.c:354: warning: deprecated conversion from string constant to 'char*'\nmake: *** [Calculator_f4e3.o] Error 1\n\nA problem was encountered while attempting to compile and install your Inline\nCPP code. The command that failed was:\n  make > out.make 2>&1\n\nThe build directory was:\n/_Inline/build/WHL/Parser/Calculator_f4e3\n\nTo debug the problem, cd to the build directory, and inspect the output files.\n\n at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nCompilation failed in require at (eval 39) line 3.\n\t...propagated at /usr/share/perl/5.10/base.pm line 92.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Tinyurl/Worker.pm line 12.\nCompilation failed in require at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\nBEGIN failed--compilation aborted\t(in cleanup) /usr/bin/perl /usr/share/perl/5.10/ExtUtils/xsubpp  -typemap /usr/share/perl/5.10/ExtUtils/typemap   Calculator_f4e3.xs > Calculator_f4e3.xsc && mv Calculator_f4e3.xsc Calculator_f4e3.c\ng++ -c   -D_REENTRANT -D_GNU_SOURCE -DDEBIAN -fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -O2 -g   -DVERSION=\\"0.00\\" -DXS_VERSION=\\"0.00\\" -fPIC "-I/usr/lib/perl/5.10/CORE"   Calculator_f4e3.c\nIn file included from Calculator_f4e3.xs:15:\n/usr/include/boost/spirit/core.hpp:18:4: warning: #warning "This header is deprecated. Please use: boost/spirit/include/classic_core.hpp"\nCalculator_f4e3.xs: In function 'double eval_expr()':\nCalculator_f4e3.xs:154: error: 'info' was not declared in this scope\nCalculator_f4e3.xs: In function 'double calc(const char*)':\nCalculator_f4e3.xs:190: error: 'exal_expr' was not declared in this scope\nCalculator_f4e3.c: In function 'void boot_WHL__Parser__Calculator_f4e3(PerlInterpreter*, CV*)':\nCalculator_f4e3.c:354: warning: deprecated conversion from string constant to 'char*'\nmake: *** [Calculator_f4e3.o] Error 1\n\nA problem was encountered while attempting to compile and install your Inline\nCPP code. The command that failed was:\n  make > out.make 2>&1\n\nThe build directory was:\n/_Inline/build/WHL/Parser/Calculator_f4e3\n\nTo debug the problem, cd to the build directory, and inspect the output files.\n\n at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser/Calculator.pm line 6.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/WHL/Parser.pm line 21.\nCompilation failed in require at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Infolavka/Default/Worker.pm line 10.\nCompilation failed in require at (eval 39) line 3.\n\t...propagated at /usr/share/perl/5.10/base.pm line 92.\nBEGIN failed--compilation aborted at /home/ash/infolavka/trunk/whoyougle/lib/Tinyurl/Worker.pm line 12.\nCompilation failed in require at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\nBEGIN failed--compilation aborted at /usr/local/apache2-dev/conf/vhosts/ash.dev line 480.\n

Во-вторых, из документации не сразу понимаешь, как обеспечить прозрачную компиляцию, когда хочется компилировать внешний файл (а не писать код прямо в Perl-файле после __DATA__). А поскольку мне нужен вывод через поток iostream из стандартной библиотеки STL, приходится конфигурировать Inline::CPP для его поддержки. Несколько впадаешь в ступор, когда понимаешь, что одновременно эти пожелания выполнить не получится. Вместо этого, оказывается, нужно записать use Inline::CPP дважды:

use Modern::Perl; use utf8; use Inline 'CPP' => Config => ENABLE => 'STD_IOSTREAM', DIRECTORY => '/tmp'; use Inline 'CPP' => "$ENV{PROJECT_ROOT}/lib/WHL/Parser/calculator.cpp";

Резюме: пользуйтесь Inline::CPP, это облегчит жизнь.