この記事は 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 を見てください。 

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.