@nqounetです。

前回に引き続き、イベント駆動型プログラミングについて考えながら書いてみました。

前回は、Mojo::EventEmitterのSYNOPSISから考えてみましたが、イマイチ腑に落ちないというか、しっくりこない感じで消化不良だったので、もう少し違う角度から見てみようと思います。

フックとしてのイベント

RPGというか、具体的にはドラゴンクエストでは、毒を受けた状態だと数歩歩くごとに毒のダメージを受けます。

歩くというイベントに対して、毒の状態の場合にダメージをうけるようなプログラムを考えてみました。

poison
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env perl
use utf8;
use 5.012;
use Encode;

package Human;
use Mojo::Base 'Mojo::EventEmitter';

sub walk {
my $self = shift;
$self->emit(message => '歩いている。');
$self->emit('walk');
return;
}

sub get_poison {
my $self = shift;
$self->emit(message => '毒を受けた!');
$self->on(walk => sub {
$self->emit(message => "毒のダメージ!");
});
}

sub cure_poison {
my $self = shift;
$self->emit(message => '毒が治った!');
$self->unsubscribe('walk');
}

package main;

my $man = Human->new;
$man->on(message => sub {
say Encode::encode_utf8($_[1]);
});

$man->walk for 1 ..2;
$man->get_poison;
$man->walk for 1 .. 2;
$man->cure_poison;
$man->walk for 1 .. 2;

多少無理矢理な気もしますが。

歩く$man->walkと、walkというイベントが発生し、そのイベントを購読した状態が毒を受けた状態で、購読をしていない状態がステータス異常が特にない、という状態です。

普通(?)に書くと以下の様な感じかなと思います。

oop_poison
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/usr/bin/env perl
use utf8;
use 5.012;
use Encode;

package Logger;
use Mojo::Base -base;

sub log {
shift;
say Encode::encode_utf8(join "\n", @_);
}

package Human;
use Mojo::Base -base;

has is_poison => 0;

sub walk {
my $self = shift;
my @results = ('歩いている。');
if ($self->is_poison) {
push @results, '毒のダメージ!';
}
return @results;
}

sub get_poison {
shift->is_poison(1);
return '毒を受けた!';
}

sub cure_poison {
shift->is_poison(0);
return '毒が治った!';
}

package main;

my $man = Human->new;
my $logger = Logger->new;

$logger->log($man->walk) for 1 .. 2;
$logger->log($man->get_poison);
$logger->log($man->walk) for 1 .. 2;
$logger->log($man->cure_poison);
$logger->log($man->walk) for 1 .. 2;

Humanクラスが毒の状態を持っていて、その状態を歩く都度確認し、毒状態であればダメージ、そうでなければ何もしない、という感じです。

拡張性を考えれば毒の状態をHumanクラスが持つのはまずいですが、今のところはそこまで考えないことにします。

当初は、Humanクラスの中でsayしていたのですが、sayが分散するのを防ごうと思った時、歩くというイベント自体よりも、ログの方にEventEmitterの強みを感じました。

通常、呼び出した先で何か異常が起きた時、それを呼び出し元で対処するのは難しいです。

EventEmitterを使った場合、Humanクラスのmessageイベントを購読しておくことで、Humanクラス内で発生させたmessageイベントを呼び出し元で簡単に扱うことができます。

また、その際、呼び出し元に対してメッセージやオブジェクトを返したりせずに、その場でイベントを発生させれば良いのでプログラムがわかりやすくなったと感じました。

エラー発生時の処理としてのイベント

そこまで考えた時に、try ~ catchのことを思い出しました。

tryは、致命的なエラーが発生するかもしれないコードを実行するときに、そのエラーをトラップして異常終了しないように使います。

もしかすると、try ~ catchではなくerrorイベントを購読してエラーをトラップする、というのがイベント駆動型の一つの使い所としてわかりやすいような気がしました。