为什么应用程序是Monad的超类?
鉴于:
Applicative m, Monad m => mf :: m (a -> b), ma :: ma
这似乎被认为是一个法律:
mf <*> ma === do { f <- mf; a <- ma; return (fa) }
或者更简洁:
(<*>) === ap
Control.Applicative
的文档说<*>
是“顺序应用程序”,这表明(<*>) = ap
。 这意味着, <*>
必须按顺序从左到右评估效果,与>>=
…保持一致。但这种感觉是错误的。 McBride和Paterson的原始文件似乎意味着从左到右的sorting是任意的:
IO monad,实际上任何Monad都可以通过使用
pure
=return
和<*>
=ap
。 我们也可以用相反顺序执行计算的ap
的变体 ,但是在本文中我们应该保持从左到右的顺序。
所以有两个合法的,不重要的派生出现在<*>
>>=
和return
,具有不同的行为。 而且在某些情况下,这两个派生都不可取。
例如, (<*>) === ap
法则强制Data.Validation定义两个不同的数据types: Validation
和AccValidation
。 前者具有类似于ExceptT的Monad
实例,以及由于在第一个错误之后停止而具有有限效用的一致Applicative
实例。 后者另一方面并没有定义Monad
实例,因此可以自由地实现一个更有用的Applicative
来积累错误。
先前在StackOverflow上已经讨论过这个问题,但是我不认为它真的有问题:
为什么这应该是一个法律?
函子,应用程序和monad的其他法则(如身份,关联等)表示这些结构的一些基本的math属性。 我们可以使用这些法则来实现各种优化,并使用它们来certificate我们自己的代码。 相反,我感觉像(<*>) === ap
法则强加一个任意的约束,没有相应的好处。
为了什么是值得的,我宁愿抛弃法律来支持这样的事情:
newtype LeftA ma = LeftA (ma) instance Monad m => Applicative (LeftA m) where pure = return mf <*> ma = do { f <- mf; a <- ma; return (fa) } newtype RightA ma = RightA (ma) instance Monad m => Applicative (RightA m) where pure = return mf <*> ma = do { a <- ma; f <- mf; return (fa) }
我认为这正确地捕捉了两者之间的关系,而没有不适当的约束。
所以,从以下几个angular度来处理这个问题:
- 还有其他法律有关
Monad
和Applicative
? - 是否有任何内在的math原因为
Applicative
sorting方式与Monad
? - GHC或任何其他工具是否执行代码转换,假定/要求这个法则是正确的?
- 为什么Functor-Applicative-Monad的提议认为这是一件非常好的事情? (引用在这里非常感谢)。
还有一个额外的问题:
-
Alternative
和MonadPlus
如何适应这一切?
注意:主要编辑澄清问题的肉。 回复@duplode引用了早期版本。
那么到目前为止,我对这个问题的答案并不是非常满意,但是我认为这些问题的意见更加引人注目。 所以我在这里总结一下:
我认为只有一个明显的Functor
实例来自Applicative
:
fmap f fa = pure f <*> fa
假设它是独一无二的,那Functor
应该成为Applicative
的超类,这是有道理的。 同样,我认为Monad
只有一个明显的Functor
实例:
fmap f fa = fa >>= return . f
所以再说一次, Functor
应该是Monad
的超类。 我曾经(而且实际上还有)的反对意见是, Monad
有两个明显的Applicative
实例,在某些特定情况下,还有更多的合法实例; 那么为什么要求呢?
pigworker ( 原Applicative
文件中的第一作者)写道:
“当然不会,这是一个select。”
(在微博上 ):“记事是对单身工作的不公正惩罚,我们应该得到应用记法”
duplode同样写道:
“……可以肯定地说,
pure === return
和(<*>) === ap
在强烈的意义上并不是法律,例如monad法则是如此……”“在
LeftA
/RightA
思想中:在标准库的其他地方有类似的情况(例如,Data.Monoid
Sum
和Product
)。与Applicative
相同的问题是,权重关系太低而不能certificate额外的精度/灵活性,新型将使应用风格使用起来不那么愉快。“
所以,我很高兴地看到这个select是明确陈述的,通过简单的推理certificate它使最常见的情况更容易。
除此之外,你问为什么Functor-Applicative-Monad
build议是好事。 原因之一是缺乏统一意味着API有很多重复。 考虑标准的Control.Monad
模块。 以下是该模块中基本上使用Monad
( MonadPlus
没有)限制的function:
(>>=) fail (=<<) (>=>) (<=<) join foldM foldM_
以下是该模块中Monad
/ MonadPlus
约束可以轻松应用于MonadPlus
/ Alternative
:
(>>) 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
后者中的许多组在Control.Applicative
, Data.Foldable
或Data.Traversable
都具有Applicative
或Alternative
版本,但是为什么首先需要了解所有的重复?
并且在我自己的(也许是错误的)直觉中,给定
pure f <*> ma <*> mb
,不需要任何预定的测序,因为这些值都不相互依赖。
值不,但效果。 (<*>) :: t (a -> b) -> ta -> tb
意味着您必须以某种方式组合参数的效果才能获得整体效果。 组合是否可交换取决于实例是如何定义的。 例如, Maybe
的实例是可交换的,而列表的默认“交叉连接”实例不是。 因此,有些情况下你不能避免强加一些命令。
Monad和Applicative有什么法律?
虽然pure === return
和(<*>) === ap
(引用Control.Applicative
)在公平的意义上并不是强制意义上的法律,例如monad法则是这样,它们有助于保持实例不令人吃惊。 鉴于每个Monad
引起了Applicative
一个实例(实际上是两个实例,正如你指出的那样),所以Applicative
的实际实例与Monad
给我们的实例是自然匹配的。 至于从左到右的约定,遵循ap
和liftM2
的顺序 (当Applicative
被引入时已经存在,并且反映(>>=)
强加的顺序)是一个明智的决定。 (注意,如果我们在实践中忽略了多less(>>=)
事情,相反的select也是可以辩护的,因为它会使(<*>)
和(=<<)
具有类似的types,按相同的顺序排列效果。)
GHC或任何其他工具是否执行代码转换,假定/要求这个法则是正确的?
这听起来不太可能,因为Applicative
甚至不是Monad
的超类。 然而,这些“法律”允许守则的读者进行同样重要的转换。
注意:如果您需要在Applicative
实例中反转效果sorting,那么就有Control.Applicative.Backwards
,就像Gabriel Gonzalez指出的那样。 另外, (<**>)
翻转参数,但是仍然从左向右排列效果,所以它也可以用来反转sorting。 同样, (<*)
不是flip (*>)
,因为这两个序列效果从左到右。
只是为了logging,标题中的问题的答案是:考虑
sequenceA :: Applicative f, Traversable t => t (fa) -> f (ta) join :: Monad m => m (ma) -> ma
什么是join . sequenceA
的typesjoin . sequenceA
join . sequenceA
?
- ATP:
Monad m, Traversable m => m (ma) -> ma
- 现状:
Applicative m, Monad m, Traversable m => m (ma) -> ma
授予, join . sequenceA
join . sequenceA
是一个人为的情况,但是肯定有一些情况下你需要一个monad,但是你也想使用Applicative
操作<*>
, *>
, <*
, <**>
等等。然后:
- 有两个单独的约束来捕获这两个操作是令人讨厌的。
-
Applicative
名称是(恕我直言)比传统的monad操作更好。 - 有两个不同的名字,例如
ap
,>>
,<<
等,是讨厌的(“哦,你不能在那里使用,这是Monad
不是一个Applicative
”;“哦,你必须使用<*>
那里,这是一个Applicative
不是一个Monad
“)。 - 在真正的monad中,顺序是非常重要的,这意味着如果
>>
和*>
做不同的事情,那么你实际上不能使用Applicative
语法,因为它会做你不期望的事情。
因此,务实地,对每一个与之兼容的Monad
(在(<*>) = ap
意义上)都是一个真正的好主意。