我知道单子是什么。我想我已经正确地理解了comonad是什么。(或者更确切地说,一个看起来很简单;棘手的部分是理解什么是有用的......)
我的问题是:某物可以是单子和共单子吗?
我预见了两个可能的答案:
- 是的,这是常见且广泛有用的。
- 不,他们从事的工作如此不同,因此没有理由想要两者兼而有之。
那么,它是什么?
我知道单子是什么。我想我已经正确地理解了comonad是什么。(或者更确切地说,一个看起来很简单;棘手的部分是理解什么是有用的......)
我的问题是:某物可以是单子和共单子吗?
我预见了两个可能的答案:
那么,它是什么?
Cofree Comonad 产生了一些对 Monads 和 Comonads 都很有用的数据结构:
data Cofree f a = a :< f (Cofree f a)
每个替代函子上的 Cofree Comonad 都会产生一个 Monad —— 请参见此处的实例:
http://hackage.haskell.org/packages/archive/free/3.4.1/doc/html/Control-Comonad-Cofree.html
instance Alternative f => Monad (Cofree f) where
return x = x :< empty
(a :< m) >>= k = case k a of
b :< n -> b :< (n <|> fmap (>>= k) m)
这给了我们,例如,非空列表作为 Monads 和 Comonads 两者(以及非空 f 分支树等)。
Identity
不是替代方案,而是Cofree Identity
产生一个无限流,实际上我们可以为该流提供不同的 monad 实例:
http://hackage.haskell.org/packages/archive/streams/3.1/doc/html/Data-Stream-Infinite.html
data Stream a = a :> Stream a
instance Comonad Stream where
duplicate = tails
extend f w = f w :> extend f (tail w)
extract = head
instance Monad Stream where
return = repeat
m >>= f = unfold (\(bs :> bss) -> (head bs, tail <$> bss)) (fmap f m)
(请注意,上面的函数不在列表中,而是在streams
包中定义)。
类似地,读者箭头不是替代品,而是Cofree ((->) r)
产生摩尔机器,摩尔机器也是单子和共单子:
http://hackage.haskell.org/packages/archive/machines/0.2.3.1/doc/html/Data-Machine-Moore.html
data Moore a b = Moore b (a -> Moore a b)
instance Monad (Moore a) where
return a = r where r = Moore a (const r)
Moore a k >>= f = case f a of
Moore b _ -> Moore b (k >=> f)
_ >> m = m
instance Comonad (Moore a) where
extract (Moore b _) = b
extend f w@(Moore _ g) = Moore (f w) (extend f . g)
那么所有这些例子背后的直觉是什么?好吧,我们免费获得了 comonadic 操作。我们得到的一元操作都是对角化的形式。有了另一种选择,我们可以<|>
一起“弄脏”结构,并在我们用完结构来弄脏时魔术“空”的东西。这让我们可以处理有限的情况。缺乏替代我们需要有无限量的结构,这样无论我们做了多少“连接”操作(我们可以认为是拼接或替换),总是有更多的空间来放置拼接的元素(比如在希尔伯特酒店:http ://www.encyclopediaofmath.org/index.php/Hilbert_infinite_hotel )。
相关地,每个Comonad 都会产生一个相关的 Monad(尽管我认为这更奇怪):
http://hackage.haskell.org/packages/archive/kan-extensions/3.1.1/doc/html/Control-Monad-Co.html
是的。将一些评论变成答案:
newtype Identity a = Identity {runIdenity :: a} deriving Functor
instance Monad Identity where
return = Identity
join = runIdentity
instance CoMonad Identity where
coreturn = runIdentity
cojoin = Identity
Reader 和 Writer 是精确的对偶,如下所示
class CoMonoid m where
comempty :: (m,a) -> a
comappend :: m -> (m,m)
--every haskell type is a CoMonoid
--that is because CCCs are boring!
instance Monoid a => Monad ((,) a) where
return x = (mempty,x)
join (a,(b,x)) = (a <> b, x)
instance CoMonoid a => CoMonad ((,) a) where
coreturn = comempty
cojoin = associate . first comappend
instance CoMonoid a => Monad ((->) a) where
return = flip (curry comempty)
join f = uncurry f . comappend
instance Monoid a => CoMonad ((->) a) where
coreturn f = f mempty
cojoin f a b = f (a <> b)
有许多有趣的结构既是 aMonad
又是 a Comonad
。
其他几个人已经在这里Identity
指出了函子,但是有一些重要的例子。
Writer
Monad
扮演一个类似的Reader
角色Comonad
。
instance Monoid e => Monad ((,) e)
instance Comonad ((,) e)
Reader
Monad
扮演一个类似的Writer
角色Comonad
。
instance Monad ((->) e)
instance Monoid e => Comonad ((->)e)
非空列表也形成单子和共单子,实际上是涉及无共单子的更大构造的特例。Identity
本案也可以看作是本案的一个特例。
还有各种基于 Kan 扩展的类似结构,它们可以转换单子和共单子,尽管它们在操作效率方面偏向于其中一个Yoneda
。Codensity
我还有一个适配器,可以将任意comonad 转换为monad 转换器。遗憾的是,在 Haskell 中不可能进行相反的转换。
在线性代数中有一个双代数的概念。理想情况下,如果我们有一个同时形成 aMonad
和 a 的东西,Comonad
并且我们想一起使用这些操作而不需要逐案推理,那么人们希望拥有它,return
并且join
是 Comonad 余代数,并且通过扩展,extract
并且duplicate
是Monad
代数。如果这些条件成立,那么您实际上可以推理同时具有Monad f
和Comonad f
约束的代码,并将每个组合子混合在一起,而无需逐案推理。
这取决于您认为“单子”是什么。如果您问“一个类型是否有可能同时成为两者的实例Monad
?Comonad
” 好的。这是一个简单的例子。
newtype Id a = Id a
instance Monad Identity where
return = Id
(Id a) >>= f = f a
instance Comonad Identity where
extract (Id a) = a
extend f ida = Id (f ida)
如果您在数学上是认真的,那么 monad 是一个三元组(X, return, bind)
,其中X
是一个类型,return
并且bind
遵循您期望的类型和规律。同样,comonad 是(X, extend, extract)
. 我刚刚证明了X
s 可能是相同的,但是由于extend
andreturn
或extract
and的类型bind
不同,它们不可能是相同的函数。所以一个数学单子永远不可能是一个共子。
扩展 Hammer 的建议,编写一个函数似乎很简单[x] -> [[x]]
。例如,
map (\ x -> [x])
会工作得很好。所以看起来列表可以形成一个comonad。啊,但是等等。那处理cojoin
,但是呢coreturn :: [x] -> x
?这大概就是为什么只有非空列表才能形成一个comonad。
这给了我们一个类型为 cobind 的函数([x] -> x) -> [x] -> [x]
。有趣的是,Hoogle 不知道这样的功能。然而我们已经有了concatMap :: (x -> [x]) -> [x] -> [x]
. 我没有看到 cobind 功能的直接用途,但我可以想象一个存在。
我仍在努力思考comonad 以及它可能有用的用途。到目前为止的答案给了我一些思考......