我读过this article,但最后一节并不明白 .
作者说Monad给了我们上下文敏感性,但是只使用Applicative实例就可以实现相同的结果:
let maybeAge = (\futureYear birthYear -> if futureYear < birthYear
then yearDiff birthYear futureYear
else yearDiff futureYear birthYear) <$> (readMay futureYearString) <*> (readMay birthYearString)
没有do语法肯定会更加丑陋,但除此之外我不明白为什么我们需要Monad . 任何人都可以为我清除这个吗?
7 回答
这里有几个使用
Monad
接口的函数 .您无法使用
Applicative
接口实现它们 . 但是为了启蒙,我们试着看看哪里出了问题 . 怎么样..看起来不错!它有正确的类型,它必须是同一个东西!我们来检查以确保..
并且's your first hint at the difference. You can'只使用复制
ifM
的Applicative
接口编写函数 .如果你把它分解为将
f a
形式的值视为"effects"和"results"(两者都是非常模糊的近似术语,这是最好的术语,但不是很好),你可以在这里提高你的理解 . 对于Maybe a
类型的值,"effect"是成功或失败,作为计算 . "result"是a
类型的值,在计算完成时可能存在 . (这些术语的含义在很大程度上取决于具体类型,所以不要认为这是除了Maybe
之外的任何其他类型的有效描述 . )鉴于这种设置,我们可以更深入地看待差异 .
Applicative
接口允许"result"控制流是动态的,但它需要"effect"控制流是静态的 . 如果您的表达式涉及3个可能失败的计算,则其中任何一个的失败都会导致整个计算失败 .Monad
界面更灵活 . 它允许"effect"控制流依赖于"result"值 .ifM
根据第一个参数选择哪个参数的"effects"包含在自己的"effects"中 . 这是ifA
和ifM
之间的巨大根本区别 .whileM
正在发生更严重的事情 . 让我们尝试制作whileA
,看看会发生什么 .嗯..发生了什么是编译错误 .
(<*>)
那里没有合适的类型 .whileA p step
的类型为a -> f a
,step x
的类型为f a
.(<*>)
不是合适的形状 . 要使其工作,函数类型必须是f (a -> a)
.你可以尝试更多的东西 - 但你最终会发现
whileA
没有任何实现,甚至接近whileM
的方式 . 我的意思是,你可以实现类型,但是没有办法使它既循环又终止 .使其工作需要
join
或(>>=)
. (好吧,或其中之一的其中一个)和那些你从Monad
界面得到的额外的东西 .使用monad,后续效果可能取决于之前的值 . 例如,您可以:
你不能用
Applicative
来做 - 一个有效计算的结果值不能确定将遵循什么效果 .有点相关:
Why can applicative functors have side effects, but functors can't?
Good examples of Not a Functor/Functor/Applicative/Monad?
As Stephen Tetley said in a comment,那个例子没有看到一个 . 这是一个简单程序的两个版本,要求您输入密码,检查您是否输入了正确的密码,并根据您是否执行了打印响应 .
让我们将其加载到GHCi中并检查monadic版本会发生什么:
到现在为止还挺好 . 但是如果我们使用适用版本:
我们输入了错误的密码,但我们仍然得到了秘密!入侵者警报!这是因为
<$>
和<*>
,或者相当于liftAn
/liftMn
,总是执行所有参数的效果 . 应用版本以do
表示法转换为应该清楚为什么这有错误的行为 . 实际上,每一个applicative functor的使用都等同于表单的monadic代码
(其中一些
appI
被允许为pure xI
形式) . 并且等效地,该形式的任何monadic代码都可以被重写为或者相当于
想一想,请考虑
Applicative
的方法:然后考虑
Monad
添加了什么:(请记住,你只需要其中一个 . )
如果你仔细想想,我们可以将应用函数放在一起的唯一方法是构造
f <$> app1 <*> ... <*> appN
形式的链,并且可能嵌套这些链(例如,f <$> (g <$> x <*> y) <*> z
) . 但是,(=<<)
(或(>>=)
)允许我们取一个值并根据该值生成不同的monadic计算,这些计算可以在运行中构建 . 这就是我们用来决定是计算"print out the secret",还是计算"print out an intruder alert"的原因,以及为什么我们不能单独用applicative functor做出决定呢?应用功能的任何类型都不允许您使用纯粹的 Value .你可以用类似的方式与_653991一起考虑
join
:as I mentioned in a comment,你可以做类似的事情当我们想要根据值选择不同的计算时会发生这种情况,但只有我们可以使用的应用功能 . 我们可以选择两种不同的计算来返回,但是他们选择了're wrapped inside the outer layer of the applicative functor. To actually use the computation we',我们需要
join
:这与之前的monadic版本完全相同(只要我们
import Control.Monad
首先获得join
):另一方面,这是一个
Applicative
/Monad
划分的实际例子,其中Applicative
具有优势:错误处理!我们显然有Monad
的Monad
实现,它带有错误,但它总是提前终止 .您可以将此视为混合值和上下文的效果 . 由于
(>>=)
将尝试将Either e a
值的结果传递给类似a -> Either e b
的函数,因此如果输入Either
为Left
,它必须立即失败 .Applicative
仅在运行所有效果后将其值传递给最终的纯计算 . 这意味着他们可以延迟访问值更长时间,我们可以写这个 .为
AllErrors
编写Monad
实例是不可能的,因为ap
匹配(<*>)
,因为(<*>)
在使用任何值之前利用运行第一个和第二个上下文来获取两个错误并将它们组合在一起 .Monad
ic(>>=)
和(join)
只能访问与其值交织在一起的上下文 . 这就是为什么Either
的Applicative
实例是左偏的,所以它也可以有一个和谐的Monad
实例 .使用Applicative,要执行的有效动作序列在编译时固定 . 使用Monad,它可以根据效果的结果在运行时变化 .
例如,使用Applicative解析器,解析操作的序列始终是固定的 . 这意味着您可以对其执行“优化” . 另一方面,我可以编写一个Monadic解析器来解析一些BNF语法描述,动态构造该语法的解析器,然后在其余输入上运行该解析器 . 每次运行此解析器时,它都可能构造一个全新的解析器来解析输入的第二部分 . Applicative没有希望做这样的事情 - 并且没有机会对尚不存在的解析器执行编译时优化...
正如您所看到的,有时候Applicative的“限制”实际上是有益的 - 有时Monad提供的额外功能是完成工作所必需的 . 这就是我们两者兼得的原因 .
如果您尝试将Monad的
bind
和Applicative<*>
的类型签名转换为自然语言,您会发现:bind
: I 将为您提供所包含的值, you 将返回一个新的打包值<*>
: You 给我一个打包的函数,它接受一个包含的值并返回一个值, I 将根据我的规则使用它来创建新的打包值 .现在您可以从上面的描述中看到,与
<*>
相比,bind
为您提供了更多控制权如果您使用Applicatives,结果的"shape"已由输入的"shape"确定,例如如果你调用
[f,g,h] <*> [a,b,c,d,e]
,你的结果将是一个包含15个元素的列表,无论变量具有哪些值 . 你没有monad的这种保证/限制 . 考虑[x,y,z] >>= join replicate
:对于[0,0,0]
,您将获得结果[]
,对于[1,2,3]
,结果为[1,2,2,3,3,3]
.