俺様エージェント指向俺様プログラミングによるエマープ生成

オブジェクト指向プログラミングの発展系として、エージェント指向プログラミングというものがある。これに対する俺様的理解は、オブジェクトが「独自のデータとインタフェース」を持っているのに対し、さらに「独自のコードとスタック」も持っているのがエージェント、それからエージェント間の通信はメッセージパッシングというもの。間違っているかもしれないけれど。
あらゆるものがオブジェクトであるようなプログラミング言語があるように、あらゆるものがエージェントなプログラミング言語を妄想したことがある。もしかしたら、そういうものはあるのかもしれないけれど。
Coro を知ってから、ひとつひとつの coro をエージェントのように扱うことができるなあ、と思っていた。メッセージのやりとりは Coro::Channel を使えばよさそうだ。
ということで、俺様理解によるエージェント指向な、俺様プログラミングで、エマープ (エマープ - Wikipedia) 生成スクリプトを書いてみた。

まず、生成したエマープを表示する generator エージェント。

use strict;
use warnings;

use Coro;
use UnauAgent;

my $generator = agent [qw/in/], sub {
    my $self = shift;
    while () {
        my $emirp = $self->recv(q{in});
        print "$emirp\n"
    }
};

agent 関数によってエージェントが作られる。第一引数の無名配列は、エージェントの通信口となるチャネルの名前のリスト。ここでは in という名前のチャネルを作ることを指示している。第二引数の無名関数は、エージェントが実行するコード。$self->recv で送られてきたエマープを受け取って表示する、という無限ループ。

次に登場するのが、素数判定エージェント primer と、エマープ生成エージェント emirper。先に emirper のコードから。

my $primer;
my $emirper; $emirper = agent [qw/in/], sub {
    my $self = shift;
    my $n = 9;
    while ($n++) {
        my $r = reverse $n;
        if ($n ne $r) {
            $self->send($primer, q{in}, $n);
            my $n_is_prime = $self->recv(q{in});
            $self->send($primer, q{in}, $r);
            my $r_is_prime = $self->recv(q{in});
            if ($n_is_prime && $r_is_prime) {
                $self->send($generator, q{in}, $n);
            }
        }
    }
};

emirper は整数値 $n と、それを逆順に並べたもの $r、それぞれを primer に send して素数かどうか判定してもらう。そして両方とも素数だったら、$n を generator エージェントに send する。

$primer = agent [qw/in/], sub {
    my $self = shift;

    my $max = 3;
    my %primes = (2 => 1, $max => 1);

    my $prime = sub {
        my $n = shift;
        foreach my $p (sort {$a<=>$b} keys %primes) {
            return if $n % $p == 0;
            return 1 if $p * $p >= $n;
        }
        return 1;
    };
    while () {
        my $n = $self->recv(q{in});
        while ($max < $n) {
            $max += 2;
            if ($prime->($max)) {
                $primes{$max} = 1;
            }
        }
        my $is_prime = defined $primes{$n} ? 1 : 0;
        $self->send($emirper, q{in}, $is_prime);
    }
};

primer エージェントは in チャネルに受信した数 $n を素数かどうか判定して emirper エージェントに結果を send する (ここは本当は in チャネルに送ってきた相手に返すようにしたい。send のとき、データだけでなく自分のアドレスとチャネル名を渡すようにすればいいはず)。

最後に、これらのエージェントが動くようにメインスレッドに細工をしなければならない (本当はメインスレッドもひとつのエージェントにしたいところ)。

schedule;

実行結果。

[takeyuki@sunya ~]$ perl emirp_agent.pl 
13
17
31
37
71
73
79
97
107
113
149
157
167
179
199
311

おっと、UnauAgent.pm の中身を載せるのを忘れていた。

use strict;
use warnings;

package UnauAgent;
use Coro;
use Coro::Channel;

sub import {
    no strict 'refs';
    *{caller()."::agent"} = *agent;
}

sub agent($&) {
    my ($names_ref, $code) = @_;
    my $agent = {};
    my $coro = new Coro($code, $agent);
    $agent->{coro} = $coro;
    foreach my $name (@{$names_ref}) {
        $agent->{channel}->{$name} = new Coro::Channel 10;
    }
    $coro->ready;                              
    bless $agent, 'UnauAgent';
}

sub _create_channel {
    my ($self, $name) = @_;
    $self->{$name} = new Coro::Channel 10;
}

sub recv {
    my ($self, $name) = @_;
    $self->{channel}->{$name}->get();
}

sub put {
    my ($self, $name, $obj) = @_;
    $self->{channel}->{$name}->put($obj);
}

sub send {
    my ($self, $peer, $channel, $obj) = @_;
    $peer->put($channel, $obj);
}
1;