Edited at
HaskellDay 10

スーパーモナドについて

More than 1 year has passed since last update.

この記事は Haskell Advent Calendar 2017 (その1) の 10 日目の記事です。

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


スーパーモナドとは

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

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

スーパーモナドを提案した論文が、次の論文です。実装の話や、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 パッケージ として提供されています。


スーパーモナドの定義

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

class Monad m where

return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b

指標付きモナドの定義も覗いてみます。指標付きモナドは次のように定義されています。3

class IxMonad m where

ireturn :: a -> m i i a
(>>>=) :: m i j a -> (a -> m j k b) -> m i k b

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

class Return m where

return :: a -> m a

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

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

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


  • Return クラスの mIxMonad クラスの m i i に対応

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

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


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

スーパーモナドを使うには、supermonad パッケージ をインストールします。モジュールファイルの冒頭には、最低限、次のような宣言を書く必要があります。

{-# LANGUAGE RebindableSyntax #-}

{-# OPTIONS_GHC -fplugin Control.Supermonad.Plugin #-}

import Control.Supermonad.Prelude

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

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


例 1: Maybe モナド

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

import qualified Prelude as P

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

instance Return Maybe where
return = P.return

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

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

foo :: Maybe Int -> Maybe String

foo x = x >>= (\x -> Just (succ x))
>>= (\x -> Just (show x))

main = print $ foo (Just 0)

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

{-# 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)

$ runghc -dynamic test.hs

[SM] Invoke (super) plugin...
[SM] Unification solve constraints...
Just "1"


例 2: IxState モナド

指標付きモナドである IxState をスーパーモナドとして扱ってみます。IxState モナドは、indexed-extras パッケージ に定義されています。

まずは、IxState モナドを普通に使うバージョンから。このプログラムをコンパイルするには、indexed-extras パッケージ が必要です。

import Control.Monad.Indexed

import Control.Monad.Indexed.State

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

main = print $ runIxState foo 0

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

{-# 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

$ 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 を見てください。