スーパーモナドについて

この記事は [Haskell Advent Calendar 2017 (その1)](https://qiita.com/advent-calendar/2017/haskell) の 10 日目の記事です。

**スーパーモナド** (supermonad) という気になる概念を見かけたので、ちょっと調べてみました。

## スーパーモナドとは

世の中には、私たちがよく知っている普通のモナド[^1] 以外にも、モナド的概念がいろいろと存在します。たとえば、こんなのがあります。[^2]

* 指標付きモナド (indexed monad)
  - https://hackage.haskell.org/package/indexed
  - https://hackage.haskell.org/package/monad-param
  - https://hackage.haskell.org/package/index-core
* 作用モナド (effect monad)
  - https://hackage.haskell.org/package/effect-monad
* 制約付きモナド (constrained monad)
  - https://hackage.haskell.org/package/rmonad

スーパーモナドは、**こうしたモナド的概念をすべて包含する概念** として提案されました。

スーパーモナドを提案した論文が、次の論文です。実装の話や、Agda で定式化した話なんかも載っています。

* J. Bracker and H. Nilsson. Supermonads: one notion to bind them all. In Proceedings of the 9th International Symposium on Haskell, pages 258-269, 2016.

実装は、[supermonad パッケージ](https://hackage.haskell.org/package/supermonad) として提供されています。

* https://hackage.haskell.org/package/supermonad

## スーパーモナドの定義

まずは普通のモナドのおさらいから。普通のモナドは次のように定義されているのでした。[^3]

```hs
class Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b
```
指標付きモナドの定義も覗いてみます。指標付きモナドは次のように定義されています。[^3]

```hs
class IxMonad m where
  ireturn :: a -> m i i a
  (>>>=) :: m i j a -> (a -> m j k b) -> m i k b 
```

そして、これらを一般化した概念であるスーパーモナドは、次のように定義されています。[^3]

```hs
class Return m where
  return :: a -> m a

class Bind m n p where
  (>>=) :: m a -> (a -> n b) -> p b
```

クラスが ``Return`` と ``Bind`` の 2 つに分かれているのが特徴的です。また、バインド演算 ``(>>=)`` も一般化されています。

スーパーモナドが普通のモナドの一般化であることは明らかです。スーパーモナドが指標付きモナドの一般化になっていることも、次のような対応がとれることからわかります。

*  ``Return`` クラスの ``m`` が ``IxMonad`` クラスの ``m i i`` に対応
*  ``Bind`` クラスの ``m``, ``n``, ``p`` がそれぞれ ``IxMonad`` クラスの ``m i j``, ``m j k``, ``m i k`` に対応

なお、普通のモナドがモナド則を満たす必要があるように、スーパーモナドもスーパーモナド則を満たす必要があります。スーパーモナド則は、普通のモナド則の自然な一般化です。

## スーパーモナドを使ってみる

スーパーモナドを使うには、[supermonad パッケージ](https://hackage.haskell.org/package/supermonad) をインストールします。モジュールファイルの冒頭には、最低限、次のような宣言を書く必要があります。

```hs
{-# LANGUAGE RebindableSyntax #-}
{-# OPTIONS_GHC -fplugin Control.Supermonad.Plugin #-}

import Control.Supermonad.Prelude
```

また、コンパイルは GHC に ``-dynamic`` オプションを指定して行います。[^4]

ここから先は、``Maybe`` モナドと ``IxState`` モナドをスーパーモナドとして扱うプログラムの例を紹介します。

### 例 1: Maybe モナド

``Maybe`` モナドのような普通のモナドについては、``Control.Supermonad.Prelude`` 内に次のような ``instance`` 宣言が書かれています。

```hs
import qualified Prelude as P

instance Bind Maybe Maybe Maybe where
  (>>=) = (P.>>=)

instance Return Maybe where
  return = P.return
```

そのため、``Maybe`` モナドなどをスーパーモナドとして扱う際は、私たちは ``instance`` 宣言を書く必要はありません。

例として、（スーパーモナドを使わない）次のプログラムを、スーパーモナドで書き直してみます。

```hs
foo :: Maybe Int -> Maybe String
foo x = x >>= (\x -> Just (succ x))
          >>= (\x -> Just (show x))

main = print $ foo (Just 0)
```

これをスーパーモナドで書き直すと、次のようになります。

```hs
{-# LANGUAGE RebindableSyntax #-}
{-# OPTIONS_GHC -fplugin Control.Supermonad.Plugin #-}

import Prelude hiding ((>>=), return)
import Control.Supermonad.Prelude

foo :: Maybe Int -> Maybe String
foo x = x >>= (\x -> Just (succ x))
          >>= (\x -> Just (show x))

main = print $ foo (Just 0)
```

```console
$ runghc -dynamic test.hs
[SM] Invoke (super) plugin...
[SM] Unification solve constraints...
Just "1"
```

### 例 2: IxState モナド

指標付きモナドである ``IxState`` をスーパーモナドとして扱ってみます。``IxState`` モナドは、[indexed-extras パッケージ](https://hackage.haskell.org/package/indexed-extras) に定義されています。

まずは、``IxState`` モナドを普通に使うバージョンから。このプログラムをコンパイルするには、[indexed-extras パッケージ](https://hackage.haskell.org/package/indexed-extras) が必要です。

```hs
import Control.Monad.Indexed
import Control.Monad.Indexed.State

foo :: IxState Int String ()
foo = imodify succ >>>= \_ -> imodify show

main = print $ runIxState foo 0
```

スーパーモナドを使うようにこれを書き換えると、次のようになります。

```hs
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# OPTIONS_GHC -fplugin Control.Supermonad.Plugin #-}

import Prelude hiding ((>>=), return)
import Control.Supermonad.Prelude
import Control.Monad.Indexed
import Control.Monad.Indexed.State

instance Bind (IxState i j) (IxState j k) (IxState i k) where
  (>>=) = (>>>=)

instance Return (IxState i i) where
  return = ireturn

foo :: IxState Int String ()
foo = imodify succ >>= \_ -> imodify show

main = print $ runIxState foo 0
```

```console
$ runghc -dynamic test1.hs
[SM] Invoke (super) plugin...
[SM] Unification solve constraints...
((),"1")
```

``instance`` 宣言を書くことで、指標モナドもスーパーモナドとして扱えることが確認できました。

[^1]: ここでは、Haskell の Control.Monad に定義されているモナドのことを「普通のモナド」と呼んでいます。Haskell のモナドはクライスリトリプルがベースだから云々、というツッコミはご容赦を…。

[^2]: [Bracker and Nilsson 2016] で紹介されているものを載せています。

[^3]: 実際の定義とは少し異なります。

[^4]: 詳しいことは [supermonad パッケージの Readme](https://hackage.haskell.org/package/supermonad#readme) を見てください。