テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(haskell report) モナドトランスフォーマ

typeclassopediaのMonad Transformerの章を読んで理解したことを示す. 誤ってたら指摘してほしい.

  • 要求: モナドを積み上げて合成したい
  • スタックの下にあるモナドの特長が優先されるようにする. 何らかのprecedenceがあることが要求.
  • MonadTrans tはliftを定義する. これは, lift :: Monad m => m a -> t m aという型であり, モナドmに対する要求を, その1つ上にあるtにまで, (名前の通り)持ち上げる.
  • 従って実装の要求は,
    1. いかなる組み合わせについてもスタックが可能であること. 言い換えると, モナドをStackableにすること(思想としてはdevice-mapperに近い)
    2. liftを何度も呼ぶのは大変鬱陶しいので, これを省略出来るようにしたい.

1)については,

  • MonadTransの型xxxTを定義する
  • xxxTが包む型mがMonadである場合, xxxT mもモナドとする. (これがt mに相当)

この時点で,

  • 積み上げ
  • liftによって特定のスタックへの操作に持ち上げる

が可能になる.

次に2)について考える.

例えばStateについて, MonadStateというStateモナドを定義する. 一般化して, MoについてMonadMoというMoモナドを定義するということにする.

以下の定義は, 「mがMonadState sであれば, MoT mはMonadState sとする」という意味である.

自動変換が出来るモナドトランスの世界では, 型は, マッチングのために逆に積まれる. 1)の世界の型としては, MoTをStateの上に積み重ねたことになるのだけど, スタックの底からマッチングを行うための工夫として, 2)の世界では型を逆に積む.

instance MonadState s m => MonadState s (MoT m) where
    get = lift get
    put = lift . put
    state = lift . state

この例では, getとした時に, そのモナドMonadState s (MoT m)であることが分かれば, getをMoTからMonadStateに持ち上げる. もちろんこれは, すでにモナドトランスの世界に入っていることを前提としているので, 一番下にあるふつうのモナドについては, 以下のようにliftしないで呼び出すことで対処する. これによって, あるモナドmの上に1段以上積み重ねられたモナドトランスは, liftを省略することが出来る.

instance Monad m => MonadState s (Strict.StateT s m) where
    get = Strict.get
    put = Strict.put
    state = Strict.state

これによって,

  • モナドが積み重なってない -> ふつうに作用が呼ばれる (MonadMoにヒットしないから)
  • 1段積み重なっている -> liftなしで呼ばれる
  • 2段以上積み重なっている -> ヒットするまでliftが繰り返される

という仕組みでliftを省略することが出来る. 例えば, StateT ReadT Writerと重なっていた場合, これは, MonadRead MonadState Writerとなる. 従って, tellが呼ばれた場合は, MonadRead (StateT m)の形でヒットして, liftが一度呼ばれる. 仮にaskが呼ばれた場合は, MonadRead mとしてヒットして, 単にaskが呼ばれる.