入れ子になった do 記法の展開

例えば JavaScript

let x = (() => {
    let y = foo;
    return bar(y);
})();

let y = foo;
let x = bar(y);

と書き換えても良かったり *1, Perl

my $x = do {
    my $y = foo;
    bar($y);
};

my $y = foo;
my $x = bar($y);

と書き換えても良かったりするように, Haskell でも

do {
    x <- do {
        y <- foo;
        bar y;
    };
    baz x
}

do {
    y <- foo;
    x <- bar y;
    baz x
}

と書き換えても良いのだろうか, という話. もし IO であるならば良さそうな気がするが, 一般のモナドでもこのような書き換えは可能なのだろうかと思った.

結論から言うと, 書き換えて良い.

なぜ

きちんと等価になっていることを示す.

まず,

do {
    x <- do {
        y <- foo;
        bar y;
    };
    baz x
}

do を使わずに書き直すと,

(foo >>= (\y -> bar y)) >>= (\x -> baz x)

さらに無駄な部分を省略すると,

(foo >>= bar) >>= (\x -> baz x)

となる.

ここでモナド(m >>= f) >>= g = m >>= (\x -> f x >>= g) を用いて書き換えると,

foo >>= (\y -> bar y >>= (\x -> baz x))

とできる. これは do を用いて

do {
    y <- foo;
    x <- bar y;
    baz x
}

と書いたものと等価になっている.

よかったね.

*1:もちろん前後で他に変数 y が使われていない場合に限るが, そういう場合は適当に変数名を変えれば良い.