最近我一直在编写FFI代码,它返回IO monad中的数据结构。例如:
peek p = Vec3 <$> (#peek aiVector3D, x) p
<*> (#peek aiVector3D, y) p
<*> (#peek aiVector3D, z) p
现在我可以想到编写代码的四种不错的方法,所有方法都密切相关:
peek p = Vec3 <$> io1 <*> io2 <*> io3
peek p = liftA3 Vec3 io1 io2 io3
peek p = return Vec3 `ap` io1 `ap` io2 `ap` io3
peek p = liftM3 Vec3 io1 io2 io3
请注意,我要求的monadic代码不需要Applicative
提供的任何内容。编写此代码的首选方法是什么?我应该使用Applicative
来强调代码的作用,还是应该使用Monad
,因为它可能(?)优化Applicative
?
由于只有[liftA..liftA3]
和[liftM..liftM5]
但我有几个记录超过三个或五个成员,这个问题有点复杂,所以如果我决定选择{{1}我失去了一些一致性,因为我必须为更大的记录使用不同的方法。
答案 0 :(得分:25)
要记住的第一件事是,这比它应该稍微复杂一点 - 任何Monad
实例应具有关联的Applicative
实例,以便{ {1}}和liftM
函数重合。因此,这里有两个指导原则:
如果您正在为任何 liftA
编写通用函数,请使用Monad
&amp; co。避免与仅具有liftM
约束的其他函数不兼容。
如果您正在使用您知道的具有Monad
实例的特定Monad
实例,请始终使用Applicative
运算符来查找您所在的任何定义或子表达式不需要Applicative
操作,但要避免漫无目的地混合它们。
我应该使用
Monad
来强调代码的作用,还是应该使用Applicative
,因为它可能(?)优化Monad
?
一般来说,如果存在差异,那将是另一种方式。 Applicative
仅支持静态&#34;结构&#34;计算,而Applicative
允许嵌入控制流程。例如,考虑列表 - 使用Monad
,您可以做的就是生成所有可能的组合并对每个组合进行转换 - 结果元素的数量完全取决于每个输入中的元素数量。使用Applicative
,您可以根据输入元素在每个步骤生成不同数量的元素,从而允许您任意过滤或扩展。
一个更有效的例子是基于压缩无限流的Monad
和Applicative
个实例 - Monad
可以简单地将它们拼接在一起,而Applicative
必须重新计算它扔掉的许多东西。
因此,最后一个问题是Monad
与liftA2 f x y
或f <$> x <*> y
等价物。我的建议如下:
Monad
而不是foo = liftA2 bar
- 它会更短,更清楚地表达您正在做的事情。最后,关于一致性的问题,如果你需要,你没有理由不能简单地定义自己的foo x y = bar <$> x <*> y
等等。