有逆变单子吗?

24

函子可以是协变的或者逆变的。这种协变/逆变的双重性质是否也适用于单子?

类似这样:

class Monad m where
  return :: a -> m a
  (>>=) :: m a -> (a -> m b) -> m b    

class ContraMonad m where
  return :: a -> m a
  contrabind :: m a -> (b -> m a) -> m b

ContraMonad类有意义吗?有示例吗?


“contrabind”是一种从尚未看到的下一个值转换为上一个值的方法。这似乎没有意义 - 我想知道它是否可用于构建反向序列,其中您的原始数据基于稍后传递给它的内容进行操作 - 但在纯函数语言中,除非它也是单子,否则无法提取它。有趣的问题。 - Benjamin Gruenbaum
2个回答

28

当然,定义它是可能的,但我怀疑它是否有任何用处。

有一句流行的说法,“单子仅仅是自函子范畴中的幺半群”。这意味着,首先,我们有一个自函子范畴(即,从某个范畴到其自身的共变函子),而且更重要的是,我们对这些自函子进行了某种乘法运算(在这种情况下是组合)。然后单子适合于我们现在不必担心的一般框架中。关键是,反变函子没有“乘法”。两个共变函子的组合仍然是一个共变函子;但是两个反变函子的组合并不是一个反变函子(相反,它是一个共变函子,因此是完全不同的东西)。

因此,“反变单子”实际上没有意义。


3
两个逆变量的组合是协变的这一点很有道理。 - leftaroundabout
将一个范畴发送到其预层范畴的对应关系是一个单子,它是反变的(这不是100%准确的,但可以使其准确)。我相信预层非常有用 :-) - fosco

18
一个逆变函子是一个从一个范畴到其对偶范畴的函子,即从一个范畴到另一个(尽管密切相关)的范畴。相比之下,单子主要是一个自函子,即从一个范畴到它自己。因此它不能是逆变的。
当你考虑单子的“基本数学”定义时,这种东西总是更加清晰明了:
class Functor m => Monad m where
  pure :: a -> m a
  join :: m (m a) -> m a

正如您所看到的,实际上没有任何箭头可以像您使用contrabind一样在结果中翻转。 当然,这并不是说

class Functor n => Comonad n where
  extract :: n a -> a
  duplicate :: n a -> n (n a)

但是共函子仍然是协变函子。

与单子不同,应用函子(幺半范畴)不需要是自函子,因此我认为这些可以被反转。让我们从“基本”定义开始:

class Functor f => Monoidal f where
  pureUnit :: () -> f ()
  fzipWith :: ((a,b)->c) -> (f a, f b)->f c  -- I avoid currying to make it clear what the arrows are.

(exercise: 定义一个基于这个的派生 Applicative 实例,然后再反过来实现)
转换方向
class Contravariant f => ContraApp f where
  pureDisunit :: f () -> ()
  fcontraunzip :: ((a,b)->c) -> f c->(f a, f b)
                            -- I'm not sure, maybe this should
                            -- be `f c -> Either (f a) (f b)` instead.

不知道这会有多少帮助。 pureDisunit 显然没有任何用处,因为它唯一的实现总是 const ()

让我们尝试编写明显的实例:

newtype Opp a b = Opp { getOpp :: b -> a }

instance Contravariant (Opp a) where
  contramap f (Opp g) = Opp $ g . f

instance ContraApp (Opp a) where
  pureDisunit = const ()
  fcontraunzip z (Opp g)
     = (Opp $ \a -> ???, Opp $ \b -> ???) -- `z` needs both `a` and `b`, can't get it!

我认为这并不是有用的,尽管你可能可以通过类似巧妙的绳结递归来定义它。

更有趣的可能是一个逆变共单子函子,但这对我来说现在太奇怪了。


你的Monad定义不完整。你必须添加fmap :: (a -> b) -> (m a -> m b),它可以被反转。 - ZhekaKozlov
不,purejoin是不够的。如果不相信,请尝试仅使用purejoin来实现bind - ZhekaKozlov
1
@ZhekaKozlov:好的,我忘记写Functor超类了。 - leftaroundabout
@ZhekaKozlov 嗯对-抱歉。 - Marcin Łoś
4
我相信ContraApp是可分的,而且非常有用:https://hackage.haskell.org/package/contravariant-1.3.3/docs/Data-Functor-Contravariant-Divisible.html - 但这与您所拥有的有些不同。 - ocharles

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