среда, 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 также для него есть своя ниша, но об этом позже.

Комментариев нет: