関数型プログラミング入門〜モナド変換子について①〜

Haskellなどの関数型プログラミングを学習していて、モナドのことが少しわかってきたかな、、と思っていたら次は「モナド変換子」という壁にぶち当たります。
オブジェクト指向に慣れ親しんだ我々平凡なプログラマに立ちはだかる第二の壁です。
Haskellでプログラムを書く場合、こいつを使わないとまともなプログラムを書くのが難しい。とってもじゃじゃ馬なやつですが、使えるととても便利なやつでもあります。

今回は「モナド変換子」の一側面を眺めて、モナド変換子の有難みを実感してみます。

## モナド変換子導入のモチベーション
あまり厳密な言い方ではないかもしれませんが、実用上は「複数のモナドの効果をもつ計算を同時に行いたいから」と言って良いかと思います。
Monad型クラスの(>>=)の型注釈は以下のように定義されていました。

```haskell
(>>=) :: Monad m => m a -> (a -> m b) -> m b
```

上記の型変数mはモナドですが、このmは全て同じモナドでなければなりません。
上記の「m a」はIO Int、「m b」はMaybe Stringのように、型変数mに対して、異なるモナドを適用するとコンパイルエラーとなります。

しかし、実際のプログラミングでは複数のモナドの演算を同じdo式内で使用したくなることがよくあります。
Haskellを始めたばかりの頃は、以下のようなコードでエラーを発生させた方は多いのではないでしょうか。

```haskell
data AppConfig = AppConfig {
  appName :: String
} deriving (Show)

appNameReader :: Reader AppConfig String
appNameReader = do  
  name <- asks appName 
  print name
  return name
```

上記のコードをコンパイルしようとすると以下のようなエラーが発生します。

```haskell
    • Couldn't match type ‘IO’
                     with ‘ReaderT AppConfig Data.Functor.Identity.Identity’
      Expected type: ReaderT AppConfig Data.Functor.Identity.Identity ()
        Actual type: IO ()
    • In a stmt of a 'do' block: print name
      In the expression:
        do name <- asks appName
           print name
           return name
      In an equation for ‘appNameReader’:
          appNameReader
            = do name <- asks appName
                 print name
                 return name
```

関数appNameReaderは、型注釈としてReader AppConfig Stringが宣言されていますので、do式でのモナド演算は(Reader AppConfig)のモナド演算としなければなりません。
一方、関数printは`print :: Show a => a -> IO ()`というIOモナドの演算です。
(Reader AppConfig)とIOという異なるモナドによる演算をdo式に記述したためエラーとなっています。

このような問題は、モナド変換子を使用することで解決できます。

## モナド変換子とは
以下は、[Real World Haskell　Chapter 18. Monad transformers](http://book.realworldhaskell.org/read/monad-transformers.html)の引用です。

>A monad transformer is similar to a regular monad, but it's not a standalone entity: instead, it modifies the behaviour of an underlying monad.

「モナド変換子はそれ自体で独立するものではなく、基盤となるモナドの振る舞いを変えるものである」、といったことが書かれています。

非常に初心者泣かせな説明ですね。実用的には「複数のモナドを一つのモナドにまとめる変換子」という理解で十分だと思います。これにより、do式で複数のモナドの演算を扱えるようになります。

モナド変換子はtransformersパッケージのMonadTrans型クラスのインスタンスとして表現されます。

```haskell
class MonadTrans (t :: (* -> *) -> * -> *) where
  lift :: Monad m => m a -> t m a
```

上記の型変数tは、あるモナドmを受け取り、変換したモナド(t m)を生成するモナド変換子です。
また、liftはモナドmの計算を(t m)の文脈に持ち上げる効果を持つ関数です。

ここで注目したいのは(t m)もまたモナドである、ということです。
なので、liftの引数である`m a`のモナド演算を持ち上げた結果である`(t m) a`もまたモナド演算です。
ということは、liftを使えば`(t m)`モナド上で、mモナドの演算を行うことができるということです。

## モナド変換子を使ってみる
先ほどのコンパイルエラーとなったコードを再度見てみましょう。

```haskell
appNameReader :: Reader AppConfig String
appNameReader = do  
  name <- asks appName 
  print name
  return name
```

`appNameReader`内の演算は`Reader AppConfig`モナドの文脈である必要がありました。
異なるモナドの演算を行うためには、先ほど見た`lift`を使って、IOモナドを`Reader AppConfig`のモナド（変換子）の文脈に持ち上げてやれば良いのです。
イメージとしては、以下のような感じです。

```haskell
-- lift :: Monad m => m a -> t m a
lift :: IO () -> ((Reader AppConfigモナド変換子) IO) ()
```

モナド変換子は「基盤となるモナドの振る舞いを変えるもの」でした。`Reader AppConfigモナド変換子 IO`で言う所の、`IO`が基盤のモナドに当たります。これに対してモナド変換子を使って、Reader rモナドの効果を合わせ持つモナドを作り出せば良いのです。

ありがたいことに、[transformers](https://hackage.haskell.org/package/transformers)パッケージでは、`Reader r`モナドのモナド変換子版を提供しています。`ReaderT r`モナド変換子がそれに当たります。

```haskell:ReaderT
newtype ReaderT r (m :: * -> *) a = ReaderT {runReaderT :: r -> m a}

instance (Monad m) => Monad (ReaderT r m) where
    return   = lift . return
    m >>= k  = ReaderT $ \ r -> do
        a <- runReaderT m r
        runReaderT (k a) r

instance MonadTrans (ReaderT r) where
    lift   = liftReaderT

liftReaderT :: m a -> ReaderT r m a
liftReaderT m = ReaderT (const m)
```

`Reader r`モナドを`ReaderT r`モナド変換子に書き換えたコードサンプルが以下です。

```haskell
appNameReader :: ReaderT AppConfig IO String
appNameReader = do  
  name <- asks appName -- (asks appName) :: (ReaderT AppConfig IO) String
  lift . print $ name -- ( lift . print $ name) :: (ReaderT AppConfig IO) ()
  return name
```

修正前の`appNameReader`は、`Reader AppConfig`のモナド演算でしたので、それ以外のモナド演算をdo式で使用することはできませんでした。
しかし、修正後は`ReaderT AppConfig IO`モナドとなっています。これは基盤となるIOモナドにReader rモナドの効果を合成したモナドとして振舞います。文字数が多いので読みにくいですが、`ReaderT AppConfig IO`自体が一つのモナドとみなせるのです。これなら、IOモナドとReader rモナドの計算を、同じdo式内で行うことができます。

基盤となるIOモナドの演算を実行するときは、liftを使うのを忘れないようにしましょう。

```haskell
  lift . print $ name -- ( lift . print $ name) :: (ReaderT AppConfig IO) ()
```

`print`はあくまでIOモナドの演算です。`lift`を使って、`ReaderT AppConfig IO`モナドの文脈に持ち上げてやる必要があります。

```haskell
print "Hello" :: IO () -- IOモナドの演算なので、このままではReaderT AppConfig IOモナドのdo式内で使用できない
-- lift :: (MonadTrans t, Monad m) => m a -> t m a
lift . print $ "Hello" :: MonadTrans t => t IO () -- liftを使うと、あるモナド演算をMonadTrans（モナド変換子）の文脈に持ち上げることができる
```

上記の型変数tはモナド変換子です。サンプルコードでいうと、`ReaderT AppConfig`にあたります。
型変数tにこれを当てはめると、以下のように読み替えられます。

```haskell
lift . print $ "Hello" :: (ReaderT AppConfig IO) () 
```

liftを使うことにより、モナドの文脈を合わせてやることで、複数のモナドの演算を使用できることが分かりましたね。
 
## 最後に
今回は「モナド変換子」の基本的な概念を見てみました。

例として、ReaderT rモナド変換子を取り上げましたが、transformersパッケージにはReaderT rモナド変換子以外にも、 [MaybeT](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/src/Control.Monad.Trans.Maybe.html)や[StateT](https://hackage.haskell.org/package/transformers-0.5.6.2/docs/src/Control.Monad.Trans.State.Lazy.html)など、様々なモナド変換子を提供しています。これらを使えば、基盤となるモナドに対してMaybeモナドやStateモナドの振る舞いも行えるモナドを手に入れることができます。
便利な世の中ですね。それでは、今回はこの辺で。