在单子范畴上,什么是函数子?

7
注意,这个问题不是关于“自函子范畴中的幺半群”的。它也不是直接涉及到Functor(Monad始终是Functor,但这个问题主要关注于monad transformers)。

docs 中关于 Haskell 的 SelectT monad transformer 的说明是:

SelectT 不是单子范畴上的函子,许多操作不能通过它进行提升。

  1. 什么是单子范畴?该范畴中的箭头是什么?
  2. 为什么一些单子变换器在单子范畴上是函子(例如 MaybeTRWST 等),但有些不是(例如 ContTSelectT)?
  3. 从编程角度来看,在单子范畴上成为函子有什么好处?作为库的使用者,我应该关心什么?

我不熟悉范畴论,但也许这意味着SelectT单子不仅仅是其基本单子的“包装器”?将IO String转换为MaybeT IO String很容易,但无法将IO String转换为SelectT <任何类型> IO String - user253751
由于单子是一个函子,箭头将会是自然变换。 - chepner
1
对于其中一些子问题,您可能会发现查看Control.Monad.Morph文档很有用。 - duplode
1个回答

4
  1. “单子范畴”是指哪个类别?该类别中的箭头是什么?

在这个类别中,对象是单子(monad),即具有类型 T,其中 T 的类型为 Type -> Type,并且具有 Monad 实例。箭头 A -> B 是它们底层函子之间的“自然变换”(natural transformations),通常在 Haskell 中用类型为 forall x. A x -> B x 的函数表示(尽管严格来说,参数性比自然性更强)。

mmorph 包中有这种类别的实现。

这个类别中的初始对象是 Identity,因为对于任何单子 T,都存在唯一一个自然变换 forall x. Identity x -> T x。双向地,我认为最终对象是 Const ()

  1. 为什么一些单子变换器是单子范畴上的函子(例如 MaybeTRWST 等),而另一些则不是(例如 ContTSelectT)?

这个类别中的函子需要一个提升后的 fmap

fmap'
  :: forall m n. (Monad m, Monad n)
  => (forall x. m x -> n x) -> forall x. T m x -> T n x

无法对于ContTSelectT 实现一般性的操作。我不确定具体原因,但似乎与方差有关:我们试图实现一个协变的函子,但 ContTSelectT 在其基础单子中是逆变的,例如在 ContT r m a 中的 (a -> m r) -> m r 中,m 既出现在正数项也出现在负数项中。
从编程角度来看,作为一个范畴上的单子函子有什么好处?作为该库的使用者,我为什么要关心它呢?
如果你有一种一般方法来在单子 n 中“运行”单子 m,你不能将其简单地扩展到 ContTSelectT 上;你只能使用更受限制的类似以下这样的映射操作:
mapSelectT :: (m a -> m a) -> SelectT r m a -> SelectT r m a
mapContT   :: (m r -> m r) -> ContT   r m a -> ContT   r m a

当基础的monad和结果类型已经确定时,你就不能随意在使用这些转换器的堆栈中提升操作。


1
那么实际上它只是函子范畴,限制为那些单子函子?我猜到了,但不确定。 - leftaroundabout
@leftaroundabout:那似乎是人们通常使用这个术语的方式,没错。我当然可以想象更奇特的东西也被称为这个名字。 - Jon Purdy
如果一个单子变换器不是函子,那么该单子就不能在单子堆栈的任意位置使用,因为你无法将其操作提升到堆栈中。然后,您只能将该单子放在堆栈的深处。请注意,涉及 IO 的所有单子堆栈都将 IO 放在堆栈的深处;这是因为 IO 实际上没有变换器。相同的限制也适用于 ContSelect 和密度单子,因为它们的变换器没有 hoist 并且不是函子。 - winitzki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接