понедельник, 28 декабря 2009 г.

Open, каналы и ожидание завершения потомка

Трудно искать черную кошку в темной комнате, особенно, если ее там нет.
Но как оказалось, еще трудней не замечать черную кошку, сидящую на видном месте в ярко освещенной комнате!

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

my $child_sub = sub { sleep };

my $child_pid = open my $fh, "-|";
defined $child_pid or die "Can't fork: $!";

if ($child_pid) {
# Родитель.
} else {
# Ребенок.
$child_sub->();
exit;
}

Впредь, если для дочернего процесса может истечь тайм-аут, буду использовать вызов pipe:

my $child_sub = sub { sleep };

pipe my $from_child_fh, my $to_parent_fh or die "pipe: $!";

my $child_pid = fork;
defined $child_pid or die "Can't fork: $!";

if ($child_pid) {
# Родитель.
close $to_parent_fh;
} else {
# Ребенок.
close $from_child_fh;
$child_sub->();
exit;
}

Или буду использовать socketpair.

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

понедельник, 21 декабря 2009 г.

Настоящие джедаи не используют threads

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

Еще на заре истории старые мастера предупреждали, что потоки, разделяемые данные, блокировки и семафоры - это не хорошо, излишне и путано. Достаточно процессов и мультиплексирования. Но не послушали их, и были созданы пользовательские потоки, внеся в этот мир сумятицу и сложность. Да и как создали? Эх...

В Linux пошли по самому легкому пути: по сути потоки являются немного облегченными процессами. Горячие головы пытались исправить это, но увидели мытарства разработчиков Solaris остудили свой пыл. И только в Windows-NT один из старых мастеров-ренегатов создал нормальные потоки. Но это была медвежья услуга - мир захотел потоков еще больше.

Даже в лагере BSD начались волнения. Потоки во FreeBSD были, но о них ядро ничего не знало со всеми вытекающими последствиями. Поэтому в основном использовали реализацию Linux потоков. Но лучшая реализация Windows потоков не давала покоя и разработчики решили, что смогут сделать то, что не удалось сделать в Solaris. Так появилась FreeBSD 5 с KSE. Потом выпустили FreeBSD 6, затем 7 и вот недавно - 8, в которой от KSE не осталось ни следа, а потоки во FreeBSD стали иметь модель как у Windows и Solaris: 1 к 1.

Но не все так мрачно. Еще по времена FreeBSD 4, один из джедаев, помня советы старых мастеров, сделал форк FreeBSD и назвал его DragonFlyBSD. Одной из целей этого проекта стала очистка от блокировок и всего, что мешает масштабированию системы. Вместо блокировок - сообщения.

И в мире повыше, чем системное программирование, есть островки надежды. Например Erlang, с концепцией легковесных процессов, общающихся посредством сообщений. Также интересен Mozart-OZ. Да и функциональные языки хорошо распараллеливаются. Эти подходы позволяют скрыть от простого программиста "сложность" мультиплексирования и межпроцессорного взаимодействия не внося при этом недостатки, свойственные потокам. А настоящие джедаи понимают, что эта сложность лишь иллюзия.

Что касается Perl, то он у меня до сих пор собран без поддержки потоков.
Ой, как в этой заметки оказался Perl? Просто эта заметка является исповедью: мне пришлось написать многопоточное приложение на Perl.
Но перед советом джедаев у меня есть два оправдания: мультиплексирующая и многопроцессная версии этого приложения!

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

Все таки был прав батюшка из фильма "Королева бензоколонки", сказав: "к полумерам не привык", либо процессы, либо мультиплексирование (ситуацию с Windows не рассматриваем).

P.S.
Перечитал - сплошной вихрь эмоций, а не заметка.

P.P.S.
Радует то, что разработчика Perl уже давно трезво смотрят на потоки. Для эмуляции системного вызова fork под Windows и для использования mod_perl2 с Apache2 в многопоточном режиме они реализовали многопоточность на основе копий интерпретатора Perl, а предыдущей модели Perl потоков забыли как о страшном сне.

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

понедельник, 14 декабря 2009 г.

Почему я не использую ORM

Для Perl на CPAN существует большое количество различным ORM, да и очень часто на конференциях делают доклады о них.
Поэтому меня время от времени возникала мысль написать cтатью "Почему я не использую ORM".

Но потом я увидел одну фразу, и смыл в данной статье отпал, так как в этой одной простой фразе содержался исчерпывающий ответ.
Вот эта фраза, сказанная http://plumqqz.livejournal.com:
"Высокоуровневый декларативный язык (SQL) в случае ORM заменяется низкоуровневым процедурным..."

От себя вкратце добавлю, что не использую в основном потому что:
1. Движки баз слишком разные. И от базы зависит не только как выбрать информацию с таблиц, но сама структура таблиц и индексов.
2. ORM - это лишний промежуточный слой...

Но, я не всегда пишу чистый SQL. Иногда генерирую его с более высокоуровневого MetaSQL, который учитывает специфику конкретного проекта. Да и MetaSQL иногда бывает частью некого декларативного описания более высокого уровня и генерируется на его основе, совместно с генерацией части Perl кода.

понедельник, 7 декабря 2009 г.

Обновил модуль BGS

Обновил модуль BGS - Background execution of subroutines in child processes.
Вместо open используется pipe - теперь дочерний процесс может печатать на STDOUT.
Также сделал, что bgs_call возвращает PID дочернего процесса. Зачем это сделал - не знаю,
наверно, надо было бы лучше добавить проверку на допустимость возвращаемого значения блока bgs_call.

понедельник, 30 ноября 2009 г.

do, eval и return

Написал

my (@foo) = do {
...
return ...
};

держа в голове

my (@foo) = eval {
...
return ...
};

А не надо было в варианте с do использовать return, так как это выход не из do, а из подпрограммы, содержащей do.

:-)

вторник, 24 ноября 2009 г.

Parrot и Rakudo - перезагрузка

Как говорилось в "Parrot: PIR, PASM и L1", разработчики Parrot вынуждены переписать всю низкоуровневую часть проекта, неизменным останется лишь PIR и PGE.

Но, как показали разработчики Rakudo в Hague grant work: the new regex engine and NQP, PGE не слишком удобен на практике. Они собираются отказаться от него в пользу NQP-rx. Соответственно, PIR ждет та же учесть, которая постигла PASM с появлением первого.

Таким образом, по моему вменению, приобретя колоссальный опыт, разработчики Parrot и Rakudo вернулись почти к той же точке, с которой начинали.
Но не все так печально. Главное есть люди, есть идеи, есть решимость.

Да наступить "Рождество" в Perl мире!

понедельник, 16 ноября 2009 г.

Weighted Random

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

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

sub wrand($) {
my $data = shift;

my $total = 0;
$total += $_ foreach values %$data;

my @dist = ();
while (my ($value, $weight) = each %$data) {
push @dist, [$value, $weight / $total];
}

return sub {
my $rand = rand;
foreach (@dist) {
return $$_[0] if ($rand -= $$_[1]) < 0;
}
return $dist[-1][0];
}
}

Пример использования:

my %foo = (
# value => weight
"one" => 3,
"two" => 2,
"three" => 1,
);

my $wrand = wrand(\%foo);
print $wrand->(), "\n" for 1 .. 10;

Если веса являются целыми числами, то можно использовать более быструю версию:

sub iwrand($) {
my $data = shift;
my %dist = ();
while (my ($value, $weight) = each %$data) {
$dist{keys %dist} = $value foreach 1 .. $weight;
}
return sub {
$dist{int rand keys %dist};
};
}

Смотрите также модуль Math::Random - Random Number Generators.

понедельник, 9 ноября 2009 г.

Светлая и темная стороны силы Perl

"Существует множество способов сделать это", и в каждой конкретной ситуации следует предпочесть наиболее простой, ясный и оптимальный вариант. Вот это белая сторона силы Perl.

На темной же стороне те "Just another Perl Hackers", кто, используя мощь Perl, делает простые вещи сложными в угоду своему самолюбию.

понедельник, 2 ноября 2009 г.

"Сегодня без..." - точка, превратившаяся в запятую

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

sub sum(&$@);
sub sum(&$@) {
my $sub = shift;
my ($sum, $h, @t) = @_;
if (defined $h) {
sum { $sub->(@_) } $sum + $h, @t;
} else {
$sub->($sum);
}
}

my @vector = 1 .. 5;
sum { print shift } 0, @vector;

Вот только эта точка, поставленная в конце цикла заметок "Сегодня без...", превратилась в запятую!

четверг, 29 октября 2009 г.

each и return - опасное соседство


keys %foo;
while (my ($k, $v) = each %foo) {
...
return ...;
}

Для вышеприведенного кода, если while вызывается более чем один раз за время существования хеша %foo, не следует забывать о сбросе итератора each при помощи keys.

Я вот забыл, так как практически не использую each, и потратил время на поиски причины, почему код выдает неверный результат. Хорошо, что сразу написал тест и увидел, что есть ошибка.

пятница, 23 октября 2009 г.

Прототипы и аргументы из списка

Вызывая подпрограмму

sub foo($$$$) { }

Так хочется вместо, например,

foo(1, $foo{1}, $foo{3}, $foo{5});

Написать

foo(1, @foo{qw(1 3 5)});

Но нельзя, так как Perl ругается, что недостаточно аргументов. :-(

четверг, 15 октября 2009 г.

PSGI и SpeedyCGI

Благодаря Алексею Капранову узнал о появлении PSGI/Plack. PSGI - cпецификация интерфейса между Perl web приложениями и web серверами. Plack - реализация.

Интересно, а SpeedyCGI (PersistentPerl) часто используют для HTTP? Для тех кто не знает: SpeedyCGI можно использовать не только для HTTP при помощи mod_speedycgi из под Apache, а как полноценный PersistentPerl для любых Perl приложений.

четверг, 1 октября 2009 г.

Reduced map

Как известно в Perl 6 имеются гипер- и редакшноператоры
(заметка о них http://laziness-impatience-hubris.blogspot.com/2009/01/perl6.html).

Но что же делать в Perl 5?

Для замены редакшноператоров можно воспользоваться подпрограммой reduce из модуля List::Util или
воспользоваться "reduced map" (rmap) из нижеприведенного модуля (List::Rmap).
Замена же гипероператоров осуществляется при помощи "hyper map" (hmap) того же модуля.

Рассмотрим примеры.

use strict;
use warnings;

use List::Rmap qw(reduce rmap hmap);

my @a = (1, 2, 3);
my @b = (4, 5, 6);

print reduce { $a + $b } @a;
# Бyдет напечатано 6
# Это в Perl6 эквивалентно [+] @x
print "\n";

# reduced map
print join " ", rmap { $a + $b } @a;
# Бyдет напечатано 1 3 6
# Это в Perl6 эквивалентно [\+] @x
print "\n";

# hyper map
print join " ", hmap { $a + $b } @a, @b;
# Бyдет напечатано 5 7 9
# Это в Perl6 эквивалентно @x >>+<< @y


А вот содержимое вышеупомянутого модуля List::Rmap:

# Reduced map

package List::Rmap;

use strict;
use warnings;

no strict qw(refs);

use Exporter;
our @EXPORT_OK = qw(reduce rmap hmap);


sub import {

# Avoid "Name "..." used only once" warnings for $a and $b.
my $caller = caller;
local *{"${caller}::a"};
local *{"${caller}::b"};

goto &Exporter::import
}



sub reduce (&@) {
my $code = shift;

my $caller = caller;
local *{"${caller}::a"} = \ my $a;
local *{"${caller}::b"} = \ my $b;

$a = shift;
foreach (@_) {
$b = $_;
$a = &{$code}();
}

return $a;
}



sub rmap (&@) {
my $code = shift;

my $caller = caller;
local *{"${caller}::a"} = \ my $a;
local *{"${caller}::b"} = \ my $b;

my @r = ($a = shift);
foreach (@_) {
$b = $_;
push @r, $a = &{$code}();
}

return @r;
}



sub hmap (&\@\@) {
my $code = shift;
my ($ra, $rb) = @_;

@$ra == @$rb or die "Different length of lists.";

my $caller = caller;
local *{"${caller}::a"} = \ my $a;
local *{"${caller}::b"} = \ my $b;

my @r = ();
foreach ($[ .. $#$ra) {
$a = $$ra[$_];
$b = $$rb[$_];
push @r, &{$code}();
}

return @r;
}


1;

пятница, 25 сентября 2009 г.

Parrot: Request Tracker и Trac Tracker

Разработчики Parrot отказались от использования Request Tracker в пользу Trac Tracker. А на душе остался осадок: ведь первый написан на Perl, а второй на Python.

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

Но может в Trac есть нечто, о чем я не знаю?

пятница, 18 сентября 2009 г.

Списки

Иногда забываю о таких замечательных модулях как List::Util и List::MoreUtils.
Почему забываю? Потому, что маленькие полезные подпрограммы по работе со списками из этих модулей легко и быстро реализовать самому.

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


use strict;
use warnings;

use List::Util;

use Benchmark qw(cmpthese);

my $foo = [1 .. 10000];

use vars qw($a $b);

cmpthese(1000, {
'manual' => sub {
my $s = 0;
$s += $_ foreach @$foo;
},
'List::Util::sum' => sub {
List::Util::sum(@$foo);
},
'List::Util::reduce' => sub {
List::Util::reduce { $a + $b } @$foo;
},
});

__END__

Rate manual List::Util::reduce List::Util::sum
manual 153/s -- -11% -91%
List::Util::reduce 171/s 12% -- -90%
List::Util::sum 1662/s 987% 871% --

четверг, 10 сентября 2009 г.

Url, обработчики и диспетчера

Url и обработчики

Первоначально web сайты целиком состояли из статических HTML страниц. Затем появился CGI, и сайты стали обзаводиться формами отправки данных и прочими динамическими станицами. Постепенно процент динамических страниц все увеличивался и увеличивался. Но url вида http://www.server.com/cgi-bin/foo.cgi не индексировались поисковиками, а также страдала эстетика, поэтому динамические страницы стали маскировать под обычные html: http://www.server.com/foo.html.

В то же время шло усложнение CGI скриптов, и повторяющийся код стали выносить в общие модули. Но не весь такой код можно было вынести в модули, в скриптах оставался код простой инициализации. Проще иметь одну точку входа, в которой посредством диспетчера вызывались обработчики конкретных команд, url.

Кстати, одну точку входа имели изначально те, что прятал статику за динамикой при помощи назначения обработчиков в Apache, а не при помощи mod_rewrite. Для тех, кто не прятал динамику за статикой, единая точка входа приводила к не слишком красивым url: http://www.server.com/index.php?action=foo, хотя при помощи mod_rewrite этого можно избежать.

Далее в статье web сайты являются лишь частным случаем.

Обработчики и диспетчера

В perl есть несколько возможностей сообщить диспетчеру какой код необходимо выполнять для каждой конкретной команды (url).

Можно просто вручную прописать таблицу диспетчеризации. Но при создании нового обработчика, можно забыть его зарегистрировать в таблице диспетчеризации, поэтому желательно чтобы эта регистрации происходила автоматически.

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

package Foo::C::Too;
use base qw(Foo::C);
sub foo : Handler(foo) { '...' }
sub too { '...' }
sub baz : Handler(abc) { '...' }

Где подпрограммы foo и baz являются обработчиками команд foo и abc или обработчиками url http://www.server.com/foo.html и http://www.server.com/abc.html, а подпрограмма too не является обработчиком.

Модуль Foo::C::Too унаследован от Foo::C, в котором определена MODIFY_CODE_ATTRIBUTES:

package Foo::C;
sub MODIFY_CODE_ATTRIBUTES {
my ($package, $sub, @attr) = @_;
foreach (@attr) {
if (m/^Handler\((.+)\)$/) {
# Регистрация в таблице диспетчеризации
# подпрограммы $sub для команды $1.
# ...
last;
}
}
return ();
}

При запуске ядро просматривает каталог Foo/C/ и загружает все модули Foo::C::*, а perl уже сам, вызывает Foo::C::MODIFY_CODE_ATTRIBUTES для каждой подпрограммы модуля, унаследованного от Foo::C.

Подробности работы с атрибутами смотрите в perldoc attributes.

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

no strict "refs";
foreach (keys %INC) {
if (m/(Foo\/C\/.+)\.pm$/) {
my $p = $1;
$p =~ s/\//::/g;
while (my ($key, $val) = each(%{*{"$p\::"}})) {
if ($key =~ m/^h_(.+)/ and my $sub = *$val{CODE}) {
# Регистрация в таблице диспетчеризации
# подпрограммы $sub для команды $1.
# ...
}
}
}
}

Соответственно в модуле Foo::C::Too, обработчиками являются h_foo и h_baz:

package Foo::C::Too;
sub h_foo { '...' }
sub too { '...' }
sub h_baz { '...' }

На практике вместо префикса "h_" лучше использовать набор префиксов, например: "u_" - пользовательские команды, "a_" - команды администраторов, "o_" - команды доступные всем.

Дополнительные свойства обработчиков можно помещать в our переменные с именами, идентичными именам подпрограмм-обработчиков.

Последний способ: имя обработчика соответствует имени модуля. Этот способ хорошо подходит тогда, когда обработчики являются не просто подпрограммами, а целыми классами. Обычно такие обработчики находятся глубоко в системе и запрятаны за каким-то простым обработчиком.

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

А если вы занимаетесь просто созданием сайтов, то посмотрите готовые фреймворки.

пятница, 4 сентября 2009 г.

О вреде программирования

Каждая профессия накладывает на человека свой специфический отпечаток. Нередко этот отпечаток имеет негативную составляющую.

Специальность программирование взращивает в человеке иллюзию легкого исправления будь чего.
Но в реальной жизни не так просто исправить что-то.

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

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

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

четверг, 27 августа 2009 г.

Perl - предотвратить случайное изменение данных

Когда необходимо предотвратить случайное изменение входных данных в плагине, можно либо передать копию данных, воспользовавшись модулем Clone, либо сделать данные неизменяемыми. Для последнего наиболее оптимальным является использование модуля Data::Lock или Hash::Util (идет в стандартной поставке Perl).

четверг, 20 августа 2009 г.

Perl7 за 7 секунд :-)

Сейчас идет разработка Perl6, а используется Perl5:

> perl -e 'print "This is Perl v$]!\n"'
This is Perl v5.008008!

Per6, Perl6 - все хотят Perl6. А я хочу Perl7! :-)

> perl -e '$] = 7; print "This is Perl v$]!\n"'
Modification of a read-only value attempted at -e line 1.

Не вышло... :-(
А если постараться?

> perl -e 'Internals::SvREADONLY($],0); $]=7; print "This is Perl v$]!\n"'
This is Perl v7!

Ура!!! Perl7 существует!!! На радостях пошел купятся в море!!!

четверг, 13 августа 2009 г.

Perl Closure - последний бастион пал

Perl предоставляет разработчику полную свободу действий и доступа к любым данным.

Правда в Perl6 частично отходят от этого принципа, но это лишь внешняя уступка современным объектно-ориентированным предрассудкам, которые легко обходятся при помощи ролей.

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

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

Рассмотрим пример:

use PadWalker;

sub foo {
my $foo = shift;
sub { print "$foo\n" };
}

my $foo = foo(3);
$foo->();

PadWalker::set_closed_over($foo, { '$foo' => \5 });
$foo->();

В этом примере мы изменили значение переменной $foo c 3 на 5.
Внимание, если эта переменная не read-only, то модификацию замыкания необходимо производить следующим способом:

PadWalker::set_closed_over($foo, { '$foo' => \ do { my $foo = 5 } });

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

четверг, 6 августа 2009 г.

Perl - когда наступит Рождество


  • Parrot 2 - начало 2010 года (по материалом Parrot рассылки для разработчиков)

  • Perl 6 - весна 2010 года (объявлено на YAPC::Europe 2009)

  • Parrot 3 - начало 2011 года

пятница, 31 июля 2009 г.

Причины популярности PHP на фоне Perl Web Frameworks

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

Истоки PHP находятся в Perl. Он один из многих. Но он "застолбил" себе расширение файлов php, в то время, как другие использовали расширение cgi в каталоге /cgi-bin/, или "цепляли" свой Perl обработчик на страницы с расширением htm или html.
PHP выделился, он стал узнаваемый - а это 80% успеха. Остальные же оставались безликим множеством.

Следующим по значению фактом является то, что в те далекие времена поисковые машины не индексировали динамику, не индексировали ничего, что находилось в каталоге /cgi-bin/. Поэтому сайты на PHP были целиком известны поисковикам, а на Perl не всегда. Ведь большинство в то время не могли использовать Perl скрипты вне каталога /cgi-bin/ или не знали о такой возможности.

Мой первый хостер для статики предоставлял url следующего вида: http://hoster/~user/, а для cgi - http://hoster/cgi-bin/user.
PHP в то время обычно также ставился в каталог cgi-bin, но Apache был настроен на обработку расширения php.
Вот и третья причина: url http://server/foo.php выглядит более красивым, чем http://server/cgi-bin/project.cgi?foo.
Кажется пустяк, но психология так не считает.

Я уверен, что если бы Perl сообществу в те далекие времена удалось бы "протолкнуть" в Apache настройки поумолчанию привязку расширения, например, plp к виртуальному обработчику /cgi-bin/plp.cgi, а с Perl поставлялся бы простой обработчик plp.cgi (обработчик-каркас), то сегодня расстановка сил была бы иной.

Но история не имеет сослагательного наклонения.

пятница, 24 июля 2009 г.

Невидимый Perl

Недавно один знакомый удивленно спросил: "А разве проект N не был статическим?". "Нет", - ответил я: "он изначально был полностью написан на Perl".

На момент создания проекта его основной целью было SEO исследование, объектом которого являлся Google.
В те далекие времена поисковики не индексировали динамику, поэтому проект извне виделся как набор статических HTML страниц.
Проект выполнил свою SEO задачу на отлично и был передан дочерней структуре.

Дальнейшая судьба проект протекала без моего участия. У проекта появились новые цели, сменился дизайн и одновременно проект был почти полностью переписан на PHP. Именно смена расширения HTML страниц вызвала вопрос с которого начата эта заметка.

Зачем я рассказал эту историю? Даже не знаю. Наверно чтобы сказать, что вокруг существует множество проектов на Perl, но не видно как и на чем они сделаны. Вот такой невидимый Perl.

пятница, 17 июля 2009 г.

Аспектно-ориентированное программирование в Perl

Существует замечательные модуль Memoize, который оборачивает указанные подпрограммы чтобы предотвратить их идентичные повторные вызовы и быстро вернуть ранее вычисленный результат. Модуль позволяет в качестве хранилища использовать не только память, но и любой другой объект, связанный с хешем. Также имеется возможность снять "обертку".

В терминах Аспектно-ориентированного программирования модуль Memoize является аспектом, который применяет к указанным подпрограммам.

Частичная реализация Аспектно-ориентированного программирования в Perl осуществляется модулем Aspect.
Этот модуль позволяет применять аспекты по маске ко множеству подпрограмм или к модулям целиком.
Кроме точки применения аспекта "before", существует также точка "after". Аспекты можно применять так, что при выходе из текущей лексической области видимости, они будет автоматически сняты.

Элементы Aspect-ориентированного программирования имеются и в Moose (Moose::Manual::MethodModifiers).
Аспекты очень гармонично сочетаются с ролями (Moose::Manual::Roles).

В Perl6 поддержка Aspect-ориентированного программирования сделана на уровне самого языка.

среда, 8 июля 2009 г.

Tailcall оптимизация в Perl5

Еду вчера вечером домой и с интересом наблюдаю за хаотичным блужданием мыслей в своей голове. Так забавно! И вдруг, в мгновение ока все замирает и тут же исчезает - остается лишь одна единственная мысль. Мысль о том, что "все-таки она вертится". То есть - существует. Это я о tailcall оптимизации в Perl5.

B одной заметке из цикла "Сегодня без..." было упомянуто, что в Per5, в отличии от Perl6, нет tailcall оптимизации. Так вот, это утверждение не совсем корректное. Да, в Perl5 нет автоматической tailcall оптимизации, но ее можно сделать вручную при помощи выражения "goto &NAME".

В качестве примера узнаем чему равен факториал десяти тысяч:

sub fact {
my ($i) = @_;
if ($i) {
return $i * fact($i - 1);
} else {
return 1;
}
}

Хотя, зачем мне знать это большое число? Да и кто на практике считать факториал рекурсией, если можно циклом. Но пример есть пример. И так, запускаем код на выполнение - Perl выводит предупреждение о слишком глубокой рекурсии и начинает "пухнуть", пытаясь вычислить факториал. Ой, память закончилась...

Перепишем подпрограмму для tailcall оптимизации:

sub fact { _fact(1, $_[0]) }
sub _fact {
my ($r, $i) = @_;
if ($i) {
return _fact($r * $i, $i - 1);
} else {
return $r;
}
}

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

А теперь воспользуемся магией "goto &NAME" и сделаем tailcall:

sub _fact {
my ($r, $i) = @_;
if ($i) {
@_ = ($r * $i, $i - 1);
goto &_fact;
} else {
return $r;
}
}

Да... Большое число вышло: 35 с половиной тысяч знаков (использовалась прагма bigint).

Вывод. Не перестаю удивляться возможностям Perl в умелых руках.

среда, 1 июля 2009 г.

Parrot: PIR, PASM и L1


PASM слишком низкоуровневый для человека и
слишком высокоуровневый для машины.

Часть критического кода, используемого виртуальной машиной Parrot, написана на PIR, а часть на С. Постоянное переключение между этими слоями: PASM (PIR) регистрами и C стеком, - существенно снижает производительность Parrot.

Поэтому сейчас ведутся работы по переписыванию некоторых частей с C на PIR. Это уже было сделано для подсистемы ввода-вывода, и что, как показала практика, повысило ее производительность.

Однако не все, что хотелось, можно переписать с C на PIR.

В связи с этим в среде разработчиков Parrot ведутся обсуждения о создании внутреннего языка специального назначения, известного под рабочим названием L1. Это язык будет максимально простым и быстрым.
Если провести аналогию с процессорами, то L1 - это аналог микрокода, на котором реализованы CISC команды поверх RICS ядра процессоров таких как x86.

По аналогии с PIR (PASM) L1 будет компилироваться в байткод L1BC, который будет исполнятся подсистемой, условно называемой nanoparrot.

Не надо пугаться, обычной PBC никуда не денется! Он будет на лету транслироваться в L1. Так что разработчики компиляторов языков высокого уровня ничего и не заметят, кроме прироста производительности! :-)

В заключении можно сказать, что:
PASM - слишком низкоуровневый для человека и появился PIR.
PASM - слишком высокоуровневый для машины и создается L1.

P.S.
Ссылки по теме:
https://trac.parrot.org/parrot/wiki/L1Recap
http://wknight8111.blogspot.com

вторник, 30 июня 2009 г.

Скорость Rakudo

Как говорилось в предыдущей заметке пришло время посмотреть на Rakudo (Parrot реализация Perl6) с практической точки зрения.

Собираем свежие Parrot и Rakudo (2009-06-30) и запускаем:

> time ./perl6 -e 'say "Just another Perl Hacker"'
Just another Perl Hacker
3.180u 0.203s 0:04.19 80.6% 4979+37881k 0+10io 0pf+0w

Что-же так долго?!

Посмотрим NQP - Not Quite Perl (6):

> cd compilers/nqp
> time ../../parrot nqp.pbc -e 'say("Just another Perl Hacker")'
Just another Perl Hacker
0.518u 0.031s 0:00.70 77.1% 31+5715k 0+10io 0pf+0w

И Perl5:

> time perl -e ' print "Just another Perl Hacker\n"'
Just another Perl Hacker
0.000u 0.010s 0:00.01 100.0% 0+0k 0+0io 0pf+0w

Подробное, исследование показало, что основное время занимает загрузка самого perl6.pir. Так что, вероятно, следует направить дальнейшие поиски причины медлительности Rakudo непосредственно в сторону Parrot.

вторник, 23 июня 2009 г.

Parrot 2009 - взгляд с 2002 года

Parrot 2009 - a 2002 point of view

Недавно на своем стареньком компьютере обнаружил parrot 2002 года (версия 0.0.9)!

Да, много воды утекло. Тогда parrot ничего не знал о PASM, а PBC создавался при помощи perl:

perl assemble.pl -o foo.pbc foo.pasm
parrot foo.pbc

Конечно этот байт-код не понятен текущей версии parrot (1.3.0).

Да что говорить о PBC, если даже PASM того времени уже большей частью не совместим с современным. Например, полностью убран "классический" вариант вызова подпрограмм - следует использовать стиль передачи продолжений (Continuation-passing style), что позволило писать более компактно.

Убрали также пользовательский стек и стеки регистров, вместо них следует использовать Array PMC.

Полностью изменились Parrot Calling Conventions. Что сделало Parrot совершенно другим, а не тем, которым я знал его в 2002 году.

Текущие состояние parrot позволяет сказать:
Да здравствуют продолжения и сопрограммы!
Да здравствуют легковесные user-level потоки!
Да здравствуют возможность dataflow execution!

И самое главное, появился PIR (Parrot Intermediate Representation), за которым прячутся все тонкости работы с подпрограммами, и с которым отпадает необходимость жонглировать регистрами.

На PASM уже никто не пишет и его не развивают. Все используют PIR!

PIR - это:
+ subroutine linkage
+ named, optional, and slurpy parameters
+ method calls
+ multimethod dispatch
+ register allocation
+ macros

Имеется также Parrot Compiler Tools - набор инструментов для написание компиляторов с различных языков. Rakudo уже давно использует его. Вероятно, скоро и сам Parrot не будет нуждаться в perl.

Parrot набрал силу. Это уже не ребенок, а отрок.
Настала пора посмотреть в сторону Perl6 (Rakudo) с практической точки зрения.

среда, 17 июня 2009 г.

Поддержка YAML в Perl

Недавно узнал, что с момента моего знакомства с YAML (1.0) у него появились новые возможности. Проверим поддержку их в Perl модулях:

YAML VERSION: 0.68
YAML::XS VERSION: 0.32
YAML::Syck VERSION: 1.07

Поддержка utf-8 реализована во всех этих модулях (у YAML::Syck - включается опционально, как и многое другое). Но есть нюансы: входные данные должны быть последовательностью байтов, а не Perl строкой (подразумевается is_utf8 флаг). На выходe YAML и YAML::XS дают YAML в виде последовательности байтов, а YAML::Syck - Perl строки. YAML::Syck может принимать YAML в виде Perl строки.

Комментарии поддерживаются в YAML::XS и YAML::Syck, а в модуле YAML нет.

Ссылки на другие элементы поддерживаются всеми модулями, а слияния - ни одним из них.

Поддержка тегов и типов данных оставляет желать лучшего, так что на практике их использовать затруднительно. Модуль YAML::Syck имеет поддержку binary типа, но только для чтения.

Производительность YAML::XS и YAML::Syck примерно одинакова.

Если сравнивать YAML и JSON, то JSON в первую очередь стоит использовать для обмена информацией между программами, а YAML - для записи и редактирования информации человеком, тем более, что YAML более нагляден, чем JSON.

P.S.
Внимание! В текущей версии YAML::XS (0.32) имеется утечка памяти.

среда, 10 июня 2009 г.

Moose Benchmark - as swift as an arrow!

Moose (постмодерн объектная система для Perl 5) с первого взгляда кажется монстром. Даже в названии: Лось - животное не маленькое. Но, как говориться, у страха глаза велики, поэтому возьмем и банально измерим как он велик и быстр.

Не будем мерить скорость загрузки, компиляции... - у нас ведь persistent perl (mod_perl, FastCGI). Да и место занимаемое в памяти не так важно важно, так как мы используем операционные системы, которые умеют Copy-on-Write. Остановимся только на быстродействии.

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

В тестировании принимали участие следующие реализации:

- классическая Perl 5;
- построенная на замыканиях - для массовки; смотрите подобный объект в заметке Объекты на замыканиях - надежная защита);
- Mouse (version 0.22) - как частичный заменитель Moose, когда есть жесткое ограничение по памяти;
- Moose (version 0.77).

Вот результаты теста (в секундах):

Perl 5 0.041
Closure 0.045
Mouse 0.074
Moose 0.105
Moose make_immutable 0.048

Моему удивлению не было предела! Работа с Mouse объектом почти в 2 раза дольше, чем с классическим, с Moose - в 2.5, Но если make_immutable, то скорость Moose близка к скорости обычных perl объектов!!!

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

Вывод. Использование Moose, при условии make_immutable, не оказывает существенного снижения производительности!

Введение в контроль версий

Предисловие

Контроль версий? А зачем он вообще нужен? Нужен, более того вы всегда используете контроль версий в той или иной степени, даже не подозревая об этом! Контроль версий - это летопись, это история. А история, как известно, - это путеводитель в будущее.

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

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

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

Экскурс в историю

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

Для этих целей была создана а начале 1970 годов программы diff. Она сравнивает два файла (оригинал и новую версию) и показывает, какие строки были изменены (diff умеет также сравнивать целые каталоги).

diff -u file_old.txt file_new.txt > file.diff

Затем автор, имея перед глазами результат работы diff, вносил правки в оригинал. Вносил, вносил, вносил... Первому надоело так делать автору программы patch, которая появилась на свет в 1985 году. Эта программа автоматически применяет патч (diff файл) к исходному файлу или каталогу:

patch < file.diff

Несмотря на солидный возраст эти программы до настоящего времени продолжают оставаться актуальными. А любой уважающий себя текстовый редактор умеет работать в diff режиме.

Вернемся к контролю версий. Хранить состояния проекта в виде полных копий слишком накладно, особенно в те далекие времена, когда объемы дисков измерялись килобайтами.
Поэтому в 1985 году используя механизмы работы diff, patch и diff3 создали RCS (Revision Control System), которая для каждого файла хранит самую последнюю версию и набор изменений между состояниями, что позволяет получить любую из предыдущих версий файла.
Однако у RCS был существенный недостаток - она не была предназначена для групповой работы, поэтому на ее основе сделали CVS (Concurrent Versions System). Но у CVS также имелись недостатки, связанные с тем, что CVS работала на уровне файлов, а не на уровне проекта целиком. В связи с этим для замены CVS была разработана более удобная в работе SVN (Subversion).

Азы "садоводства"

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

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

Рассмотрим пример. Ваша команда, работая на стволе, выпустила первую версию продукта - в репозитории поставили метку RELENG_1_0_REL (RELENG - это сокращение от release engineering) и продолжили работать, двигаясь ко второй версии. В это время в релизе 1.0 обнаруживается досадная оплошность, тогда вы от метки RELENG_1_0_REL, создаете ветку RELENG_1, на которой исправляете ошибку и выпускаете версию 1.1, поставив в репозитории метку RELENG_1_1_REL. Затем оказывается, что одно реализованное на стволе важное новшество можно легко перенести в версию 1.1, так как выход версии 2.0 будет не скоро, вы принимаете решение, выпустить версию 1.2, содержащую это новшество. Для этого необходимые изменения сливаются со ствола на ветку RELENG_1, и с выпуском релиза 1.2 ставиться метка RELENG_1_2_REL.

Кроме вышеупомянутых веток, можно создавать так называемые экспериментальные ветки, на которых обкатываются какие-то уж очень неординарные новшества проекта. Если эти оказались полезными, то код с экспериментальной ветки сливают на ствол, иначе ветку "забрасывают".

Если непосредственно перед релизом требуется дополнительная подготовка, например документации или части исходников, то релиз RELENG_1_0_REL делают не на стволе, а непосредственно на ветке RELENG_1, место отщепления которой маркируют тегом RELENG_1_BP (break point). Этот тег необходим, чтобы, например, можно было посмотреть различия ветки от ствола в момент отщепления.

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

Особенности систем контроля версий

Системы контроля версий можно разбить на два типа: централизованные и распределенные. К централизованным относятся CVS, Subversion (он же SVK), к распределенным - Darcs, Mercurial, Git.

Основные особенности централизованных систем:

1. Все дерево проекта храниться в одном едином репозитории.
2. Можно получить лишь необходимую часть проекта, ветки.
3. Для работы необходимо быть online.

Основные особенности распределенных систем:

1. Каждая ветка проекта обычно - это отдельный репозиторий.
2. Нельзя получить часть проекта - только репозиторий полностью.
3. Можно работать offline.

Рассмотрим подробней каждый из 3 пунктов.

1. У централизованой системы создание ветки - это очень легкая операция, а сами ветки - всего лишь изменения. А у распеределенной системы, чтобы сделать ветку, необходимо создать полноценную копию репозитория. Чтобы минимизировать занимаемое место набор распределенных репозиториев оптимизируют за счет использования жестких ссылок файловой системы. Эту операцию желательно повторять после масштабных слияний между локальными ветками.

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

3. Необходимость быть online при современном развитии коммуникаций можно считать незначительным требованием. Это требование становиться еще мягче, если учесть, что можно работать не со всем репозиторием, а лишь с необходимой частью, Да и самые часто используемые команды: state и diff - можно использовать offline.

Работая с распределенной системой, не следует забывать, что чем дольше находишься offline, тем выше вероятность возникновения конфликтов. Кстати, конфликты проще решать, когда для каждого независимого участка работы используется отдельная локальная копия репозитория.

Если вы используете централизованную систему контроля версий планируете находиться вне сети продолжительное время, то можно перейти в режим создания патчей. К слову, манипулировать патчами удобно при помощи Darcs, который представляет собой скорее систему обмена патчами, нежели систему контроля версий. В крайнем случае, поверх централизованной системы всегда можно сделать распределенную, например SVK поверх SVN.

Подводя итоги, можно сказать, что выбор между централизованной и распределенной системой контроля версий целиком зависит от стиля работы команды. И централизованные, и распределенные системы имеют как преимущества так и недостатки, но ни один из них не является критичным. Совместить достоинства обоих подходов обещают в Subversion 2, добавив некоторые элементы распределенных систем.

среда, 3 июня 2009 г.

Объекты на замыканиях - надежная защита

Perl не препятствует прямому обращению и управлению данными объектов. Просто существует договоренность, что так делать не хорошо. Если все-таки необходимо явно возвести препятствие прямому доступу, можно воспользоваться Inside-out объектами. Однако Inside-out содержат лазейку, которая позволяет заглянуть за кулисы объекта (смотрите "Закрытые объекты" в Perl5 и Perl6).

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

Рассмотрим в качестве примера объект, конструктор которого принимает число, и который имеет два метода: too - возвращает инициализирующие число, baz - возвращает результат сложения с указным числом:

package Foo;

sub new {
shift;
my $foo = shift;
my $self = \$foo;
bless $self;
return $self;
}

sub too {
my $self = shift;
return $$self;
}

sub baz {
my $self = shift;
return $$self + shift;
}

package main;
my $foo = Foo->new(2);
print $foo->too(), "\n"; # 2
print $foo->baz(3), "\n"; # 5

Замыкание с аналогичным функционалом выглядит следующим образом:

sub new_foo {
my $foo = shift;
return sub {
my $method = shift;
if ($method eq "too") {
return $foo;
} elsif ($method eq "baz") {
return $foo + shift;
}
};
}

my $foo = new_foo(2);
print $foo->("too"), "\n"; # 2
print $foo->("baz", 3), "\n"; # 5

Вызов методов объекта-замыкания несколько необычен для тех, кто привык к объектам на основе классов. К счастью, Perl очень гибкий языки и позволяет устранить вышеупомянутое "неудобство", "посвятив" быть объектом непосредственно само замыкание:

package Foo;

sub new {
shift;
my $foo = shift;
my $self = sub {
my $method = shift;
if ($method eq "too") {
return $foo;
} elsif ($method eq "baz") {
return $foo + shift;
}
};
bless $self;
return $self;
}

foreach my $method (qw(too baz)){
no strict 'refs';
*$method = sub { my $self = shift; return $self->($method, @_) };
}

package main;
my $foo = Foo->new(2);
print $foo->too(), "\n"; # 2
print $foo->baz(3), "\n"; # 5

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

среда, 27 мая 2009 г.

Сегодня без того - не знаю чего

На этот раз Perl Фея дала мне время окончательно проснуться, а не шептала сонному задание на ушко.
После предыдущей встречи, описанной в http://laziness-impatience-hubris.blogspot.com/2009/05/return_20.html, я был готов ко всему что угодно, но такого даже не мог представить. Увидев, что я открыл глаза, Фея подошла, приложила свой тоненький пальчик к кончику моего носа, загадочно подмигнула и тихо прошептала: "А сегодня без того - не знаю чего".

Я смотрел на нее как баран на новые ворота, а она села на край дивана, взяла с полки книгу о виноградарстве и стала ее неторопливо листать. Через некоторое время, видя, что я еще нахожусь в замешательстве от задания, Фея сказала: "Я подожду тут - на улице ведь ливень, а ты не отвлекайся, работай".

Прошла минута или больше, а я все еще продолжал стоять на месте как вкопанный. "Кстати, вот тут, при описании процесса формирования высокоштамбового двуплечного кордона со свисающим приростом, забыли упомянуть про резервные сучки замещения. А ты ведь недавно посадил четыре куста винограда и собираешься придавать им эту форму, так что обрати внимание на возможность быстрой замены поврежденных рукавов", - Фей взглянула в мою сторону и ее улыбка вернула мне способность мыслить. "Какая все таки разносторонне образованная Фея!", - подумал я и спросил: "Насчет задания... Я не понимаю, что ты хочешь чтобы я сделал".

"Ну как не понимаешь? Ты, наверно, еще не проснулся. В позапрошлый раз я попросила тебя сделал "без return", затем - "без return, но наоборот". Сегодня сделай что-то нечто среднее! Все очень просто", - лукаво улыбнулась она и опустила взгляд в книгу.

Легко сказать, что-то среднее... Возвращать не возвращая, вызывать не вызывая... Точно! Придумал! Надо вызывать возвращаемое, не передавая информацию как вызывать! Если бы я не был так увлечен придуманным решениям, то я бы заметил, как Фея украдкой одобрительно посмотрела на меня. Нет, феи, не способны читать мысли людей, но они способны тонко чувствовать их.

И так, через минуту код из предыдущих заданий (http://laziness-impatience-hubris.blogspot.com/2009/05/return.html, http://laziness-impatience-hubris.blogspot.com/2009/05/return_20.html) был преобразован к следующему виду:

sub mul(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] * $$y[$_] } 0 .. $#$x;
return sub { $sub->(@r) };
}

sub minus(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] - $$y[$_] } 0 .. $#$x;
return sub { $sub->(@r) };
}

sub say(&@) {
my $sub = shift;
print join(" ", @_), "\n";
return sub { $sub->() };
}

sub main() {
my @i = (1, 2, 3);
my @j = (2, 3, 4);
my @k = (3, 4, 5);

return mul { minus { say {} @_ } @_, @k } @i, @j;
}

my $sub = \&main;
$sub = $sub->() while $sub;

Подпрограммы-кирпичики не возвращают ссылку на подпрограммы-клей и результаты свой работы. Они возвращают ссылку на подпрограммы, которые в свою очередь вызывают подпрограммы-клей, передавая им в качестве аргументов результат работы подпрограммы-кирпичика. Таким образом "большая вызывалка" ничего не знает о результатах работы подпрограммы-кирпичиков.

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

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

"Ну посмотри, я же вплотную подвела тебя к...", - не успела Фея закончить фразу, как я ее перебил: "... к событийно-управляемому стилю программирования БЕЗ каких либо глобальных структур данных!"
"Молодец, все верно. Стоит лишь заменить return на регистратор обработчиков, а "большую вызывалку", как ты ее смешно называешь, - на генератор или цикл ожидания событий...", - продолжила она.

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

Вот, а вы говорите, что Фей нет. Они есть, к тому же очень симпатичные и обворожительные!

среда, 20 мая 2009 г.

Сегодня без return - выворачиваем наизнанку

Похоже это уже становиться традицией - задавать задачки спозаранку.
Оказывается, существует Perl Фея, которая по утрам, когда пора вставать, будит всех Just another Perl Hacker.
О прошлом появлении Феи читайте в http://laziness-impatience-hubris.blogspot.com/2009/05/return.html

Она очень осторожна, но сегодня, устроив засаду, я ее увидел первый раз.
Фей, поняв, что я не сплю, от неожиданности смутилась, похвалила меня за решение задачи "Сегодня без return",
и тут же кокетливо спросила:
- А сможешь наоборот?!
- Это как? - удивился я.
- Ну, ведь это ты Perl Hacker, а не я - придумай, - стрельнув зелеными глазами ответила она и улетела.

Да... Озадачила...
И так, в прошлый раз мы избавились от return и это наш резерв.
Если наоборот, то возвращаем все return, и используем их чтобы, чтобы...
Неужели чтобы предотвратить вызовы подпрограммы!!!

Что-то тут не так - совсем без вызовов невозможно.
Может стоит лишь упорядочить их, централизовать в одном месте?
И будет один "большой вызов", не множество "маленьких".

Возьмем пример из предыдущей заметки и перепишем его так, что подпрограммы-кирпичики не вызывают подпрограммы-клей,
а возвращают их вместе с результатом своей работы. Получиться цепочка из подпрограмм.
А "большая вызывалка" будет получать в цикле ссылки на подпрограммы и вызывать их, передавая идущие с ними аргументы.
И так до самого конца, до пустой подпрограммы.

sub mul(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] * $$y[$_] } 0 .. $#$x;
return $sub, @r;
}

sub minus(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] - $$y[$_] } 0 .. $#$x;
return $sub, @r;
}

sub say(&@) {
my $sub = shift;
print join(" ", @_), "\n";
return $sub;
}

sub main() {
my @i = (1, 2, 3);
my @j = (2, 3, 4);
my @k = (3, 4, 5);

return mul { minus { say {} @_ } @_, @k } @i, @j;
}

# А вот и "большая вызывалка", которая все это запускает.
my $sub = \&main;
my @param = ();
do {
($sub, @param) = $sub->(@param);
} while ($sub);

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

return \&mul, sub { \&minus, sub { \&say, undef, @_ }, \@_, \@k }, \@i, \@j;

Но в этом случае, мы лишаемся магии прототипов, так что первый вариант - предпочтительней.

Чувствую, что Фея будет довольна. Интересно только, зачем она меня подталкивает в этом направлении?
Хотя, всему свое время: скоро, наверно, узнаю.

среда, 13 мая 2009 г.

Сегодня без return

Подозрения оправдались. История продолжается, после описанных в http://laziness-impatience-hubris.blogspot.com/2009/05/blog-post.html и http://laziness-impatience-hubris.blogspot.com/2009/05/2-tail-call.html
цветочков пошли ягодки.

Итак сегодня работаем без return!

В качестве примера умножим поэлементно пару векторов и отнимем третий:

(1, 2, 3) >>*<< (2, 3, 4) >>-<< (3, 4, 5)

Для этого в perl6 имеются гипероператоры - http://laziness-impatience-hubris.blogspot.com/2009/01/perl6.html.

Но сейчас разговор не о perl6, а о perl5. Вот код, который выполняет вышеупомянутые действия:

sub mul(\@\@) {
my ($x, $y) = @_;
map { $$x[$_] * $$y[$_] } 0 .. $#$x;
}

sub minus(\@\@) {
my ($x, $y) = @_;
map { $$x[$_] - $$y[$_] } 0 .. $#$x;
}

my @i = (1, 2, 3);
my @j = (2, 3, 4);
my @k = (3, 4, 5);

my @ij = mul(@i, @j);
my @r = minus(@ij, @k);
print join(" ", @r), "\n";

Хотя в этом коде return явно не прописан он присутствует.
Обе подпрограммы возвращают результаты в виде списков.

Теперь попробуем избавиться от return. Сразу говорю, что модификация передаваемого по ссылки массива, - это не наш путь.
Не для того существует perl, чтобы на нем писать с C стиле.

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

mul(@i, @j, sub { minus(@_, @k, sub { say(@_, sub {}) }) });

Или вот так, чтобы было удобней работать с параметрами подпрограмм,
ссылку на подпрограмму сделаем первой, а не последней среди передаваемых параметров:

mul(sub { minus(sub { say( sub {}, @_) }, @_, @k) }, @i, @j);

Что, при использовании магии прототипов, можно записать следующим образом:

mul { minus { say {} @_ } @_, @k } @i, @j;

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

sub mul(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] * $$y[$_] } 0 .. $#$x;
$sub->(@r);
}

sub minus(&\@\@) {
my ($sub, $x, $y) = @_;
my @r = map { $$x[$_] - $$y[$_] } 0 .. $#$x;
$sub->(@r);
}

sub say(&@) {
my $sub = shift;
print join(" ", @_), "\n";
$sub->();
}


my @i = (1, 2, 3);
my @j = (2, 3, 4);
my @k = (3, 4, 5);

mul { minus { say {} @_ } @_, @k } @i, @j;

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

Подпрограммы в вышеприведенном коде можно разбить на два типа:
1) подпрограммы-кирпичики (mul, minus, say);
2) подпрограммы-клей (анонимные подпрограммы).

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

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

четверг, 7 мая 2009 г.

Сегодня без циклов, часть 2 - tail call оптимизация

Продолжение истории, начало - http://laziness-impatience-hubris.blogspot.com/2009/05/blog-post.html.

Сегодня я не был разбужен мыслью: "сегодня без ...".
Сегодня меня разбудило порицание: "а-я-яй, рекурсия..., а где же tail call оптимизация?!"

- Отстань, - отвечаю я: в perl нет поддержки tail call оптимизации".
- В perl5 - да, но в perl6 будет!
- Ладно, вот вариант с оптимизаций:

my @vector = 1 .. 5;

sub _sum {
my ($s, $h, @t) = @_;
@t ? _sum($s + $h, @t) : $s + $h;
}

sub sum { _sum(0, @_) }

print sum(@vector), "\n";

Краткое пояснение к коду: tail call оптимизация используется когда функция завершается вызовом другой.

Сильно подозреваю, что завтра будет продолжение! :-)

среда, 6 мая 2009 г.

Сегодня без циклов

Сегодня мое пробуждение от сладкого сна было грубо и бесцеремонно нарушено невесть откуда прилетевшей мыслью: "сегодня без циклов"!

"Без циклов, так без циклов", - в ответ подумал я.

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

my @vector = 1 .. 5;

# Вариант с циклом.
{
my $sum = 0;
$sum += $_ for @vector;
print $sum, "\n";
}

# Вариант без цикла.
{
sub sum {
my ($h, @t) = @_;
@t ? $h + sum(@t) : $h;
}

print sum(@vector), "\n";
}

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

Интересно, а вдруг и завтра я буду разбужен мыслью: "сегодня без ...", и это "без ..." будет намного сложней?
Но, как говориться, утро вечера мудренее.

четверг, 15 января 2009 г.

Гипер-, редакшн- и кроссоператоры в Perl6

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

Еще в 1748 году М. В. Ломоносов открыл закон сохранения магии. Или все-таки вещества? - да какая разница! Этот закон гласит: "Так, ежели где убудет несколько магии, то умножится в другом месте".

В соответствии с законом сохранения магии в Perl6, когда многие вещи стали простыми, появились очень интересные новые возможности. Среди нововведений - гипероператоры, reduction и cross операторы. Это операторы предназначены для работы со списками. Кстати, подобные им операторы изначально имеются в магических языках семейства APL.

Наличие таких нововведений в Perl6 позволяет выражать мысли более компактно и ясно. Рассмотрим некоторые примеры и сравним их с примерами на языке J (http://jsoftware.com), который является потомком языка APL. Для простоты в нижеприведенных примерах код обозначен отступом вправо, а результат работы - отступом влево.

Примеры выполнены при помощи Rakudo (компилятор Perl 6, основанный на Parrot) Rakudo - сокращение от японского Rakuda-do, что означает "Путь верблюда".

Перед основыми примерами создаем списки (массивы, вектора):

Perl | J
------------------------------------------------
@x = (1 .. 3) | x=. 1 2 3
@y = (4 .. 6) | y=. 4 5 6

В J списки создаем вручную, чтобы не забегать вперед.


Гипероператоры

Гипероператоры производят операции над списками поэлементно. Смотрите http://perlcabal.org/syn/S03.html#Hyper_operators. Сложение двух списков при помощи гипероператора и прибавление к списку числа:

Perl | J
------------------------------------------------
@x >>+<< @y | x + y
5, 7, 9 | 5 7 9
@x >>+>> 3 | x + 3
4, 5, 6 | 4 5 6

В J оператор является аналогом Perl гипероператора, что не удивительно, ведь APL - язык векторных вычислений.


Reduction операторы

Эти операторы работают не с единичными элементами списка, а с учетом результата действий над предыдущими элементами. http://perlcabal.org/syn/S03.html#Reduction_operators

Perl | J
------------------------------------------------
[+] @x | +/ x
6 | 6

Для достижения аналогичного результата в J оператор модифицируется наречием "поэлементно".

Следующий пример еще не работает в Rakudo (parrot revision 35576). Отличие этого примера от предыдущего в том, что сумма считаеться не для всего списка, а для каждой его части:

Perl | J
------------------------------------------------
[\+] @x | +/\ x
1, 3, 6 | 1 3 6

В J это делается при помощи наречия "префиксно".


Cross операторы

Эти операторы производят действие над каждой комбинацией элементов списков.
http://perlcabal.org/syn/S03.html#Cross_operators

Сделаем фрагмент таблицы умножения:

Perl | J
------------------------------------------------
@x X*X @y | x */ y
4, 5, 6, 8, 10, 12, 12, 15, 18 | 4 5 6
| 8 10 12
| 12 15 18

В J оператор модифицируется наречием "таблично". Отличие Perl от J в том, что в J сразу создается матрица, а Perl придется список преобразовать в таблицу. Просто у Perl и J разное распределение магии.

Выводы

Как видим, Perl получил в свой арсенал мощные возможности для работы со списками, Нельзя сказать, что они соизмеримы с возможностями векторных языков, но их вполне хватает для "бытовых" нужд.

Для "профессиональных" векторных вычислений рекомендуется использовать Perl Data Language (http://pdl.perl.org).