ListモナドとMaybeモナドに続いて、今日はIOモナドのお勉強をしてみます。
実装
Stateモナドと同じ定義をしました。
my $io_monad = Monad->new( T_arrow => sub { my $arrow = shift; # A -> B return sub { # TA -> TB my $tx = shift; # TA return sub { # TB my $world = shift; my ($value, $new_world) = $tx->($world); return $arrow->($value), $new_world; }; }; }, eta => sub { my $value = shift; # A return sub { # TA my $world = shift; return $value, $world; }; }, mu => sub { my $ttx = shift; # TTA return sub { # TA my $world = shift; my ($tx, $new_world) = $ttx->($world); return $tx->($new_world); }; }, );
IOモナドを使うための関数
副作用を持つ関数を、通常の値を受け取ってIOモナドの値*1を返す関数として定義します。
1. print文
undef を表す IOモナド値 を返す関数として定義します。このIOモナド値は、世界を「printが施された状態」に変化させます。ただし、RealWorld値をどう実装すべきかわからなかったので、世界を名前(WORLD、等)として実装しました。副作用を施すと新しい世界になるので、この名前をWORLD' とかとします。*2
sub io_print{ # A -> T A my $x = shift; return sub { my $world = shift; print Dumper $x; return undef, $world . q('); }; }
2. 引数を読む
コマンドライン引数は世界の状態によって変わるのでIOモナド値で表されます。この値は世界に依存しますが、参照しても世界は変更されません。
なお、引数$xはdo表記を楽にするためのダミーです。
sub io_args{ # A -> T A my $x = shift; return sub { my $world = shift; return \@ARGV, $world; }; }
3. ファイルを読む
2. と同様です。*3
sub io_readfile{ my $x = shift; return sub { my $world = shift; open my $fh, '<', $x; my $val = do {local $/; <$fh>;}; close $fh; return $val, $world; }; }
使い方
準備ができましたので、使ってみましょう。引数に渡されたファイルを読み、そのファイルに含まれるバイト数を数えてみます。
まず、第一引数を取り出すための関数とバイト数を数えるための関数を用意します。どちらも普通の関数です。
sub fst{ my $ref = shift; return $ref->[0]; } sub size{ my $str = shift; return length $str; }
次に、前回のdo表記で処理を作ります。これは全てIOモナドの世界に持ち上げられ、 T A -> T B と言う関数になります。
my $task = do_ \&io_args, # 引数を得る ( comp $io_monad->eta, \&fst ), # 最初の引数を使う \&io_readfile, # ファイルを読む ( comp $io_monad->eta, \&size ), # サイズを数える \&io_print, # 出力する ;
この関数を評価して値を得ます。これが、Haskellのmainであり、アプリケーションはひとつのIOモナド値として表されます。IOモナド値は環境によって値が決まり、環境を変化させる値なので、我々がアプリケーションに持っているイメージと一致しますね。
なお、最初に渡す値はundefを表現するIOモナド値である eta->(undef) としました。
my $main = $task->( $io_monad->eta->(undef) );
では、このIOモナド値は今我々が居る環境ではどういう値として具現化されるのか、評価してみましょう。最初に書いたように今回は世界を名前として扱ってますので、'WORLD'と言う値を今の我々の世界につけたものとして、これを渡してみます。
my ($value, $world) = $main->('WORLD'); print "value: ", $value, "\n"; print "world: ", $world, "\n";
この結果は、
% perl io_monad.pl io_monad.pl $VAR1 = 1650; Use of uninitialized value in print at io_monad.pl line 104. value: world: WORLD'
となりました。渡したファイルのサイズは、1650 byteでした。
実はアプリケーションに対して我々が期待するのは、その値ではなく世界の変化です。結果は、画面に表示されて我々の目に入ってきます。つまり、画面の更新がアプリケーションに求めているものです。これを反映するように、値としてはundef値、そして、【ファイルサイズが表示された新しい「WORLD'」と言う世界】が返ってきました。