Haskell 为什么Applicative应该是Monad的超类?

Haskell 为什么Applicative应该是Monad的超类?,haskell,monads,applicative,fam-proposal,Haskell,Monads,Applicative,Fam Proposal,鉴于: 这似乎被认为是一项法律: Applicative m, Monad m => mf :: m (a -> b), ma :: m a 我认为这正确地抓住了两者之间的关系,而没有过度地限制两者 因此,从以下几个角度来探讨这个问题: 是否存在与Monad和Applicative相关的其他法律 对于Applicative的效果排序,是否存在与Monad相同的内在数学原因 GHC或任何其他工具是否执行假定/要求该定律为真的代码转换 为什么这项建议被认为是一件绝好的事情?(在此将不

鉴于:

这似乎被认为是一项法律:

Applicative m, Monad m => mf :: m (a -> b), ma :: m a
我认为这正确地抓住了两者之间的关系,而没有过度地限制两者

因此,从以下几个角度来探讨这个问题:

  • 是否存在与
    Monad
    Applicative
    相关的其他法律
  • 对于
    Applicative
    的效果排序,是否存在与
    Monad
    相同的内在数学原因
  • GHC或任何其他工具是否执行假定/要求该定律为真的代码转换
  • 为什么这项建议被认为是一件绝好的事情?(在此将不胜感激)
还有一个额外的问题:

  • Alternative
    MonadPlus
    如何适应这一切

注:主要编辑以澄清问题的实质。@duplode发布的答案引用了早期版本

根据我自己(可能是错误的)直觉,给定
纯f ma mb
,不需要任何预先确定的顺序,因为这些值都不相互依赖

值没有,但效果有<代码>()::t(a->b)->ta->tb意味着您必须以某种方式组合参数的效果以获得整体效果。组合是否可交换取决于实例的定义方式。例如,
的实例可能是可交换的,而列表的默认“交叉连接”实例则不是。因此,在某些情况下,你无法避免强加一些命令

与单子和应用程序相关的法律(如有)是什么


虽然可以公平地说,
pure===return
()==ap
(quoting)并不是严格意义上的定律,例如monad定律,但它们有助于保持实例不出人意料。假设每个
Monad
都会产生
Applicative
的一个实例(正如您所指出的,实际上是两个实例),那么
Applicative
的实际实例自然会与
Monad
提供的匹配。至于从左到右的惯例,遵循(在引入
Applicative
时已经存在,并且反映了
(>>=)
强加的顺序)是一个明智的决定。(请注意,如果我们暂时忽略实践中
(>>=)
的重要性,那么相反的选择也是可以辩护的,因为它会使
()
(< P>)除此之外,您还问为什么<代码>函子应用的MaNad 建议是一件好事。一个原因是因为缺少统一意味着API有很多重复。考虑标准<代码>控件。MaNad < /Cult>模块。以下是基本上使用<代码> Monad < /代码>的功能。(对于
MonadPlus
)约束没有:

newtype LeftA m a = LeftA (m a)
instance Monad m => Applicative (LeftA m) where
  pure = return
  mf <*> ma = do { f <- mf; a <- ma; return (f a) }

newtype RightA m a = RightA (m a)
instance Monad m => Applicative (RightA m) where
  pure = return
  mf <*> ma = do { a <- ma; f <- mf; return (f a) }

后一组中的许多人确实有
Applicative
Alternative
版本,包括
Control.Applicative
Data.Foldable
Data.Traversable
——但为什么首先需要学习所有这些重复呢?

好吧,我对目前给出的答案不太满意,但我认为nk随附的评论更有说服力。因此,我将在这里总结:


我认为在
Applicative
中只有一个合理的
Functor
实例:

(>>=) fail (=<<) (>=>) (<=<) join foldM foldM_
(>>) return mzero mplus mapM mapM_ forM forM_ sequence sequence_ forever
msum filterM mapAndUnzipM zipWithM zipWithM_ replicateM replicateM_ guard
when unless liftM liftM2 liftM3 liftM4 liftM5 ap
所以再次强调,
Functor
应该是
Monad
的一个超类是有道理的。我的反对意见(事实上,我现在仍然反对)是,有两个合理的
应用的
实例来自
Monad
,在某些特定的实例中,甚至更多的实例是合法的;那么为什么要强制一个呢

(第一作者)写道:


“当然不是这样。这是一种选择。”

(关于):“do符号是对在单子中工作的不公正惩罚;我们值得应用符号”

类似地写道:

“…可以公平地说,
纯===return
()==ap
不是严格意义上的定律,例如,单子定律是如此…”


“关于
LeftA
/
RightA
思想:在标准库的其他地方有类似的案例(例如
Sum
Product
Data.Monoid
中)。对
Applicative
执行相同操作的问题是,权重与权重的关系太低,无法证明额外的精度/灵活性。新类型将使Applicative样式使用起来不那么愉快。”


因此,我很高兴看到这一选择得到了明确的表述,并通过简单的推理证明了这一选择的合理性,即它使最常见的案例变得更容易。

为了记录在案,标题中问题的答案是:考虑

fmap f fa = fa >>= return . f
join.sequenceA的类型是什么

  • ATP:
    Monad m,可遍历的m=>m(ma)->ma
  • 当前情况:
    Applicative m,Monad m,Traversable m=>m(ma)->ma
  • 诚然,
    join.sequenceA
    是一种人为的情况,但在某些情况下,您确实需要单子,但您也希望使用
    Applicative
    操作
    *>
    *>
    执行不同的操作,那么您就不能实际使用
    Applicative
    语法,因为它会做一些你意想不到的事情
    因此,从实用角度讲,为每个与之兼容的
    (在
    ()=ap
    意义上)设置一个
    是一个非常非常非常好的主意。

    如果应用程序无效,那么对
    的评估顺序并不重要。但是,如果它也是一个(非常重要的)monad,那么它必须是有效的。你可以做一些愚蠢的事情,比如扔掉左方论证中的效果,但是如果你有monad,
    ap
    是一个
    (>>) return mzero mplus mapM mapM_ forM forM_ sequence sequence_ forever
    msum filterM mapAndUnzipM zipWithM zipWithM_ replicateM replicateM_ guard
    when unless liftM liftM2 liftM3 liftM4 liftM5 ap
    
    fmap f fa = pure f <*> fa
    
    fmap f fa = fa >>= return . f
    
    sequenceA :: Applicative f, Traversable t => t (f a) -> f (t a)
    join :: Monad m => m (m a) -> m a