モノイドの双対はコモノイドです。モノイドが (同形のもの) として定義されていることを思い出してください。
class Monoid m where
create :: () -> m
combine :: (m,m) -> m
これらの法律で
combine (create (),x) = x
combine (x,create ()) = x
combine (combine (x,y),z) = combine (x,combine (y,z))
したがって
class Comonoid m where
delete :: m -> ()
split :: m -> (m,m)
いくつかの標準操作が必要です
first :: (a -> b) -> (a,c) -> (b,c)
second :: (c -> d) -> (a,c) -> (a,d)
idL :: ((),x) -> x
idR :: (x,()) -> x
assoc :: ((x,y),z) -> (x,(y,z))
のような法律で
idL $ first delete $ (split x) = x
idR $ second delete $ (split x) = x
assoc $ first split (split x) = second split (split x)
この型クラスが奇妙に見えるのには理由があります。インスタンスがあります
instance Comonoid m where
split x = (x,x)
delete x = ()
Haskell では、これが唯一のインスタンスです。reader を writer の正確な双対として再キャストできますが、comonoid のインスタンスは 1 つしかないため、標準の reader 型と同型の何かが得られます。
すべての型をコモノイドにすることで、「デカルト閉圏」の「デカルト」圏になります。「モノイド閉カテゴリ」は CCC に似ていますが、このプロパティがなく、部分構造型システムに関連しています。線形論理の魅力の 1 つは、これが示す対称性の向上です。一方、部分構造型を使用すると、より興味深いプロパティ (リソース管理などのサポート) を持つコモノイドを定義できます。実際、これにより、C++ でのコピー コンストラクターとデストラクタの役割を理解するためのフレームワークが提供されます (ただし、C++ ではポインターが存在するため、重要なプロパティは強制されません)。
編集: コモノイドからのリーダー
newtype Reader r x = Reader {runReader :: r -> x}
forget :: Comonoid m => (m,a) -> a
forget = idL . first delete
instance Comonoid r => Monad (Reader r) where
return x = Reader $ \r -> forget (r,x)
m >>= f = \r -> let (r1,r2) = split r in runReader (f (runReader m r1)) r2
ask :: Comonoid r => Reader r r
ask = Reader id
上記のコードでは、バインド後にすべての変数が 1 回だけ使用されることに注意してください (したがって、これらはすべて線形型で型付けされます)。モナドの法則の証明は自明であり、コモノイドの法則のみが機能する必要があります。したがって、Reader
本当に は に双対Writer
です。