Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

Stateモナドとオブジェクト

面白くもなんともないParsonオブジェクト。

package Parson;
sub new {
    my ($class, $self) = @_;
    bless $self => $class;
}

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

sub get_name {
    my ($self) = @_;
    $self->{name};
}

my $p = Parson->new({});
$p->change_name("Jony");
print $p->get_name, "\n";

ポリモフィズム気にしないなら以下のように書いても同じ。

my $p = {};
change_name($p, "Jony");
print get_name($p), "\n";

$p 毎回つけるのうざいって発想からStateモナドが使える。change_nameとget_nameから$selfの引数が消えてて、内部で勝手に拾ってる感じになってる。

package StateParson;
sub change_name {
    my ($name) = @_;
    sub {
        my ($self) = @_;
        (undef, {%$self, name => $name});
    };
}

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

my $p = {};
my $f = comp(
    \&change_name,
    \&get_name
);
my $m = $f->("Jony");
(my $name, $p) = $m->($p);
print $name, "\n";

ただ、クライスリ射になるので「;」でつなげては駄目で、つなげるのにcompが必要になって、ちょっとめんどい。compは以下のような実装になる。

sub comp($$) {
    my ($f, $g) = @_;
    sub {
        my ($value) = @_;
        sub {
            my ($self) = @_;
            ($value, $self) = $f->($value)->($self);
            $g->($value)->($self);
        };
    };
}

モナドの最大の弱点は組み合わせに弱いことなので、例えばParsonとFoodの両方をStateモナドの枠組みで扱おうとすると割と酷い目に遭う。

逆にStateモナドを、単に状態を引数にとるフラットな関数に戻すこともできる。例えばHaskellでは以下のような実装でよい。

flat :: (a -> State s b) -> s -> a -> (b, s)
flat f s a = let m = f a
             in runState m s

以下のx1がStateモナドを使ったもので、x2が普通の関数に戻して同じ処理を書いたもの。モナドを使わない方を見れば実に簡単なことしかしてないことがわかる。

import Control.Monad.State

type MyObject = String

add :: String -> State MyObject ()
add s = do
  orig <- get
  put $ orig ++ s

x1 :: MyObject
x1 = flip execState "" $ do
       add "abc"
       add "EFG"

x2 :: MyObject
x2 = let add' = flat add
         s = ""
         ((), s')  = add' s  "abc"
         ((), s'') = add' s' "EFG"
     in s''