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