一个幺半群的对偶是一个类群。回想一下,幺半群被定义为(同构的东西)
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 只有一个实例,因此我们得到了与标准 reader 类型同构的东西。
让所有类型都成为共形是使“笛卡尔封闭类别”中的类别“笛卡尔”的原因。“Monoidal Closed Categories”类似于 CCC,但没有此属性,并且与子结构类型系统有关。线性逻辑的部分吸引力在于增加的对称性,这是一个例子。同时,具有子结构类型允许您定义具有更多有趣属性的 comonoids(支持资源管理之类的东西)。事实上,这为理解 C++ 中复制构造函数和析构函数的作用提供了一个框架(尽管由于指针的存在,C++ 并没有强制执行重要的属性)。
编辑:来自comonoids的读者
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
请注意,在上面的代码中,每个变量在绑定后只使用一次(因此这些都将使用线性类型)。monad 定律证明是微不足道的,只需要 comonoid 定律起作用。因此,Reader
真的是对偶Writer
。