为什么Kleisli不是Monoid的实例?

21

如果您希望附加两个类型为(a -> m b)的函数,以便获得仅附加两个结果的相同类型的一个函数,那么您可以使用Kleisli:

instance (Monad m, Monoid b) => Monoid (Kleisli m a b) where
    mempty = Kleisli (\_ -> return mempty)
    mappend k1 k2 =
        Kleisli g
            where
                g x = do
                    r1 <- runKleisli k1 x
                    r2 <- runKleisli k2 x
                    return (r1 <> r2)

然而,目前在Control.Arrow中没有这样的实例定义。

通常在Haskell中,我怀疑有一个很好的理由,但找不到原因。

注意

这个问题与这个问题非常相似。 但是,对于Monoid,我没有看到定义如下实例的方法:

instance (Monad m, Monoid b) => Monoid (a -> m b) where
    [...]

因为已经存在一个实例:

instance Monoid b => Monoid (a -> b) where
    [...]
1个回答

29

在图书馆设计的业务中,我们面临一个选择点,并选择在我们的集体政策(或其缺乏)中不完全保持一致。

Monad(或Applicative)类型构造函数的Monoid实例可以以多种方式出现。点对点的提升始终可用,但我们没有定义。

instance (Applicative f, Monoid x) => Monoid (f x) {- not really -} where
  mempty         = pure mempty
  mappend fa fb  = mappend <$> fa <*> fb
请注意,instance Monoid (a -> b)仅是一个点对点提升,因此当m bMonoid实例对b进行点对点提升时,(a -> m b)的点对点提升确实会发生。
我们通常不进行点对点提升,这不仅是因为它会阻止其他Monoid实例,这些实例的承载体恰好是应用类型,而且还因为f的结构通常被认为比x的结构更重要。一个关键案例是自由幺半群(更好地称为[x]),它由[](++)组成的Monoid,而不是通过点对点提升。单子结构来自列表包装,而不是被包装元素。
我的经验法则确实是优先考虑类型构造器内在的幺半群结构,而不是点对点提升或特定实例的幺半群结构,例如a -> a的组合幺半群。它们可以并且确实会得到newtype包装。
人们争论是否应该在存在Monoid (m x)MonadPlus m的情况下使它们重合(类比于Alternative)。 我的感觉是唯一好的MonadPlus实例是Monoid实例的副本,但其他人不同意。 但是,库在这个问题上并不一致,特别是在...中出现了我常见的老问题。
对于Maybe的幺半群实例,它忽略了我们通常使用Maybe来模拟可能的失败,并且注意到如果一个半群没有中立元素,则可以使用相同的数据类型想法插入一个额外的元素来给出一个中立元素。 这两种构造导致同构类型,但它们并非概念上相关。(编辑:更糟糕的是,这个想法实现起来很棘手,需要一个Monoid约束条件,而只需要一个Semigroup。 我希望看到基于Semigroup扩展到Monoid的想法得到实现,但不适用于Maybe。)
回到具体的Kleisli,我们有三个明显的候选实例:
1. return和Kleisli组成的Monoid (Kleisli m a a) 2. MonadPlus m => Monoid (Kleisli m a b)->上点对点提升mzeromplus 3. 把b的幺半群结构提升到m然后到->Monoid b => Monoid (Kleisli m a b) 我预计还没有作出选择,只是因为不清楚该做出哪种选择。 我犹豫要这么说,但我的投票会是2,优先考虑来自Kleisli m a的结构,而不是来自b的结构。

你有“f的结构通常被认为比f的结构更重要。”这是错误吗? - Rein Henrichs
感谢您发现了这个笔误。不,那不是Maybe的恶梦。那只是雪上加霜的侮辱。 - pigworker
啊,我懂了。谢谢你。顺便提一下,这是使用“Option”在“semigroups”包中实现的,它是围绕“Maybe”进行封装的新类型包装器。 - Rein Henrichs
@ReinHenrichs,我刚才看了一下,情况变得更糟了。概念一:可能失败的影响以及通过优先选择来管理它的业务,这是计算的一个概念。概念二:将中性元素与半群相邻以获得单子,这是价值的一个概念。这种修复尝试并没有成功地解开这些概念。 - pigworker
1
@ReinHenrichs 你可以通过不同的称呼来表示同构类型(例如,通过newtype实现)。名称是为了表达这个概念,为了整个语义结构的一系列,而类型本身只是承载者。我们通过使用实例推断来找到正确的语义管道,以命名概念为关键,从而获得了很多节省精力的能力。混淆概念是一件令人遗憾的事情。当你想知道“前者将如何被不同地表示”时,你会错过大局。 - pigworker
显示剩余4条评论

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