【问题标题】:Are there contravariant monads?有逆变单子吗?
【发布时间】:2024-01-15 14:17:01
【问题描述】:

函子可以是协变的和逆变的。这种协变/逆变对偶也可以应用于单子吗?

类似:

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 是从您尚未看到的下一个值到上一个值的转换。这没有任何意义——我想知道它是否可以用于构建反向序列,其中你的原始数据是根据你后来传递给它的内容来操作的——但在纯函数式语言中,它无法提取它除非它也是一个单子。有趣的问题。

标签: haskell monads category-theory


【解决方案1】:

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

有一种流行的说法是“monad 只是一类内函子中的一个幺半群”。它的意思是,首先,我们有一个内函子类别(意思是,从某个类别到自身的(协变)函子),而且,我们在这个内函子上有一些乘法(在这种情况下 - 组合)。然后 monad 适合一些我们现在不必担心的通用框架。关键是,逆变函子没有“乘法”。两个协变函子的组合又是一个协变函子;但是两个逆变函子的组合不是逆变函子(而是协变函子,所以,完全不同的野兽)。

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

【讨论】:

  • 关于两个逆变器的组合是协变的好点。
  • 将一个类别发送到其预滑层类别的对应是一个单子,并且它是逆变的(这不是 100% 准确,但可以使其准确)。我相信预滑轮非常有用:-)
【解决方案2】:

逆变函子是从一个类别到其opposite category 的函子,即从一个类别到另一个类别(尽管密切相关)。 OTOH,monad 主要是一个 endofunctor,即从一个类别到 itself。所以它不能是逆变的。

当您考虑 monad 的“基本数学”定义时,这类东西总是会变得更加清晰:

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)

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

与 monads 不同,applicatives (monoidal functors) 不需要是 endofunctors,所以我相信这些 可以 被扭转。让我们从“基本”定义开始:

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.

(练习:根据this定义派生的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),可以颠倒。
  • 不,purejoin 还不够。如果不相信,请尝试仅根据 purejoin 来实现 bind
  • @ZhekaKozlov:当然,我忘了写 Functor 超类。
  • @ZhekaKozlov 嗯是的 - 对不起。
  • 我相信 ContraApp 是可分割的,它非常有用:hackage.haskell.org/package/contravariant-1.3.3/docs/… - 但它与你所拥有的有点不同