21

如果您希望附加两个 (a -> mb) 类型的函数,以便只获得一个附加两个结果的相同类型的函数,则可以使用 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
    [...]
4

1 回答 1

28

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

MonoidMonad(或)类型构造函数的实例Applicative可以通过多种方式产生。逐点提升总是可用的,但我们没有定义

instance (Applicative f, Monoid x) => Monoid (f x) {- not really -} where
  mempty         = pure mempty
  mappend fa fb  = mappend <$> fa <*> fb

请注意,instance Monoid (a -> b)只是这样的逐点提升,因此(a -> m b)每当幺半群实例 form b对单群群 on 进行逐点提升时,都会发生逐点提升 for b

我们一般不做逐点提升,不仅因为它会阻止其他Monoid实例的载体恰好是应用类型,而且因为f通常认为x. 一个关键的例子是自由幺半群,更好地称为[x],它是一个Monoidby []and (++),而不是通过逐点提升。monoidal 结构来自列表包装,而不是来自包装的元素。

我首选的经验法则确实是将类型构造函数中固有的幺半群结构优先于逐点提升或类型的特定实例化的幺半群结构,例如组合幺半群 for a -> a。这些可以并且确实得到newtype包装。

争论是否Monoid (m x)应该与MonadPlus m两者都存在时一致(并且与 类似Alternative)。我的感觉是,唯一好的MonadPlus实例是实例的副本Monoid,但其他实例不同。尽管如此,图书馆在这件事上并不一致,尤其是在(很多读者会看到我的这个老熊来了)…

... 的幺半群实例Maybe,它忽略了我们通常Maybe用来模拟可能的故障的事实,而是观察到相同的数据类型想法可以使用插入额外元素的相同数据类型的想法来给半群一个中性元素,如果它没有'已经有一个了。这两种结构产生了同构类型,但它们在概念上不是同源的。(编辑更糟糕的是,这个想法实现起来很尴尬,给实例一个Monoid约束,当只Semigroup需要 a 时。我希望看到Semigroup-extends-to-Monoid想法实现,但不是Maybe

具体来说Kleisli,我们有三个明显的候选实例:

  1. Monoid (Kleisli m a a)returnKleisli 组成
  2. MonadPlus m => Monoid (Kleisli m a b)提升mzeromplus逐点->
  3. Monoid b => Monoid (Kleisli m a b)解除b以上的m幺半群结构->

我预计没有做出任何选择,只是因为尚不清楚该做出哪个选择。我犹豫要不要这么说,但我的投票是 2,优先考虑来自Kleisli m a的结构而不是来自 的结构b

于 2015-10-04T17:31:07.270 回答