import Data.Monoid

data Stamp m a = Stamp a m

instance Monoid m => Monad (Stamp m) where
    return a = Stamp a mempty
    (Stamp a m) >>= f = let Stamp b n = f a in Stamp b (m <> n)

stamp :: Monoid m => m -> Stamp m ()
stamp = Stamp ()

runStamp :: Stamp m a -> (a, m)
runStamp (Stamp a m) = (a, m)

任意のモノイドからモナドが作れると聞いて書いてみた。monoidal stamping monadと言うそうです。

: 追記

ありがたいことに訂正をいただいた。

つまりこのStampWriterそのものであるらしい。実際、mtlでのWriterの定義1を見ると

newtype Writer w a = Writer (a, w)
instance (Monoid w) => Monad (Writer w) where
    ...

であり、Monadの実装も一致している。

自由モノイド[a]はWriter

monoidal stamping monadはWriterそのもの

>>> runStamp $ do { stamp "foo" ; (do { stamp "bar" ; return "baz" }) >>= stamp }
((), "foobarbaz")
>>> runWriter $ do { tell "foo" ; (do { tell "bar" ; return "baz" }) >>= tell }
((), "foobarbaz")

左自明モノイドFirst aは単一代入

>>> runStamp $ do { stamp (First (Just 'a')) ; stamp (First (Just 'b')) ; return 'c' }
('c', First (Just 'a'))

右自明モノイドLast aは破壊的代入

>>> runStamp $ do { stamp (Last (Just 'a')) ; stamp (Last (Just 'b')) ; return 'c' }
('c', First (Just 'b'))

自明モノイド()はIdentity

>>> runStamp $ do { stamp () ; stamp () ; return 42 }
(42, ())

面白いですね。

詳しく

モノイド$(M,\cdot,e)$に対し

  • 関手 $X \rightarrowtail X \times M$
  • 単位元 $\eta(x) := x \times e$
  • 乗法 $\mu((x \times n) \times m) = x \times (m \cdot n)$

とすると、これはモナド(monoidal stamping monad)になる。

同様にcomonoidからcomonadが作れるらしい。

参考

  1. ここではWriterTを使わない古いものを用いた