Вычисление рекуррентных соотношений на Perl

от автора

Привет,
в этом посте я расскажу рецепт добавления функциональности в Перл.

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

 f(0) = 1 f(n) = n * f(n-1) 

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

factorial(0) ->     1; factorial(N) ->     N * factorial(N-1).

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

#!/usr/bin/perl -I./lib
use strict;
use bigint;
 
use Recurrent;
 
recurrent fib => {
    arg(0) => lambda { my($n) = @_; return 0 },
    arg(1) => lambda { my($n) = @_; return 1 },
    arg(n) => lambda { my($n) = @_; return fib($n1) + fib($n2) },
};
 
print fib(1000);

Из примера видно, что у нас появились новые функции recurrent, arg, n и lambda. На самом деле, практическая польза есть только у recurrent, все остальные нужны лишь для получения более «красивого» кода.

Давайте напишем модуль Recurrent.pm

 package Recurrent; our $VERSION = '0.01'; use base qw(Exporter);  use strict; use Carp qw(croak);  our @EXPORT = qw(arg n lambda recurrent);  sub arg($)    { $_[0] } # возвращает первый аргумент sub n         { ''    } # возвращает пустую строку sub lambda(&) {         # alias для sub { }      return shift; } sub recurrent($$) {      my($name, $mapping) = @_;     croak '$name should be a string'         if ref($name) ne '';     croak '$mapping should be a hash reference'         if ref($mapping) ne 'HASH';     croak 'no parametric function in recurrent relation'         if ref($mapping->{(n())}) ne 'CODE';     {         no strict 'refs';                  # создаем кеш и функцию $name         my $mem = join('::', (caller())[0], "RECURRENT_CACHE_$name");         my $fun = join('::', (caller())[0], "$name");                  *{$mem} = {};         *{$fun} = sub {             my($_n, $_mapping) = ($_[0], $mapping);             croak "argument is required for $name(n)"                 if !defined $_n;                              # ищем значение в кеше, если нет то вычисляем             defined(${*{$mem}}->{$_n})                 ?  (${*{$mem}}->{$_n})                 :  (${*{$mem}}->{$_n} =                     defined($_mapping->{$_n})                         ?  ($_mapping->{$_n}->($_n))                         :  ($_mapping->{(n())}->($_n)));         };     } }  1; 

Теперь, можно написать что-то вроде.

#!/usr/bin/perl -I./lib
use strict;
use bigint;
 
use Recurrent;
 
recurrent fac => {
    arg(0) => lambda { my($n) = @_; return 1 },
    arg(n) => lambda { my($n) = @_; return $n * fac($n1) },
};
 
print fac(1000);

ссылка на оригинал статьи http://habrahabr.ru/post/169469/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *