среда, 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).