29

我知道单子是什么。我我已经正确地理解了comonad是什么。(或者更确切地说,一个看起来简单;棘手的部分是理解什么是有用的......)

我的问题是:某物可以是单子共单子吗?

我预见了两个可能的答案:

  • 是的,这是常见且广泛有用的。
  • 不,他们从事的工作如此不同,因此没有理由想要两者兼而有之。

那么,它是什么?

4

5 回答 5

26

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

http://comonad.com/reader/2011/monads-from-comonads/

于 2013-05-14T20:50:27.277 回答
24

是的。将一些评论变成答案:

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)
于 2013-05-14T20:34:45.067 回答
17

有许多有趣的结构既是 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 扩展的类似结构,它们可以转换单子和共单子,尽管它们在操作效率方面偏向于其中一个YonedaCodensity

我还有一个适配器,可以将任意comonad 转换为monad 转换器。遗憾的是,在 Haskell 中不可能进行相反的转换。

在线性代数中有一个代数的概念。理想情况下,如果我们有一个同时形成 aMonad和 a 的东西,Comonad并且我们想一起使用这些操作而不需要逐案推理,那么人们希望拥有它,return并且join是 Comonad 余代数,并且通过扩展,extract并且duplicateMonad代数。如果这些条件成立,那么您实际上可以推理同时具有Monad fComonad f约束的代码,并将每个组合子混合在一起,而无需逐案推理。

于 2013-05-19T18:57:50.303 回答
6

这取决于您认为“单子”是什么。如果您问“一个类型是否有可能同时成为两者的实例MonadComonad” 好的。这是一个简单的例子。

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). 我刚刚证明了Xs 可能是相同的,但是由于extendandreturnextractand的类型bind不同,它们不可能是相同的函数。所以一个数学单子永远不可能是一个共子。

于 2013-05-14T20:36:53.773 回答
1

扩展 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 以及它可能有用的用途。到目前为止的答案给了我一些思考......

于 2013-05-16T07:45:48.390 回答