モナド則1が成り立たないと

以前「モナド則1(左単位元)を満たさない偽リストモナド」モナド則1が成り立たないとどういうとき困るかを書いたのですが通常の場面では出てこないような例だったので別の例をあげます。

あらためて偽リストモナドの定義を書くと

import Monad(sequence)

data MyList a = My { unMy :: [a] } deriving (Show,Eq)

instance Monad MyList where
  return x = My [x,x]
  (My xs) >>= k = My $ xs >>= (unMy.k)

この偽モナドモナド則3は満たしていますがモナド則1、2を満たしていません。

もしモナド則すべてを満たしているならば次のコード

 do
  x1 <- My [1]
  x2 <- My [2]
  x3 <- My [3]
  x4 <- My [4]
  x5 <- My [5]
  return (sum [x1,x2,x3,x4,x5])

は sequence関数を使って

do
  xs <- sequence [My [1], My [2], My [3], My [4], My [5]]
  return (sum xs)

と書き換えることができます。

(大雑把にいうと sequence [m1,m2... m_n] は do {x1 <- m1; x2 <- m2; ... x_n <- m_n; return [x1,x2... x_n]} と同じ意味になります)

しかし実際にはモナド則1を満たしていないので

(上に書いた例だと結果が長くなるので x1 x2 だけにした例で試します)

> do {x1 <- My [1]; x2 <- My [2]; return (sum [x1,x2])}
My {unMy = [3,3]}

> do {xs <- sequence [My [1], My[2]]; return (sum xs)}
My {unMy = [3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3]}

と結果が違ってきます。

これは sequence の定義

sequence :: Monad m => [m a] -> m [a]
sequence = foldr mcons (return [])
             where mcons p q = p >>= \x -> q >>= \y -> return (x:y)

の中で return が使われているためモナド則1の破れの影響を受けるからです。(この偽リストモナドの場合は return がかかる度リストの要素数が倍々になっていきます。)

この例のようにモナド則1が成り立たないと内部で return を使っている関数を使っての書き換えができなくなってしまいます。