To 引用我自己的话 https://stackoverflow.com/a/12309023/334519:
那么,当我们有了 monad 时,为什么还要费心应用函子呢?
首先,根本不可能提供 monad 实例
我们想要使用的一些抽象——Validation
是个
完美的例子。
其次(相关地),这只是一个可靠的开发实践
完成工作的最弱的抽象。在
原则上这可能允许优化,否则不会
可能,但更重要的是它使我们编写的代码更多
可重复使用的。
对第一段进行一些扩展:有时您无法在单子代码和应用代码之间进行选择。查看其余部分那个答案 https://stackoverflow.com/a/12309023/334519讨论为什么您可能想要使用 ScalazValidation
(没有也不可能有 monad 实例)来建模
验证。
关于优化点:可能需要一段时间才能在 Scala 或 Scalaz 中普遍相关,但请参见示例Haskell 的文档Data.Binary http://hackage.haskell.org/package/binary-0.7.1.0/docs/Data-Binary-Get.html:
应用风格有时可以产生更快的代码,例如binary
将尝试通过将读取分组在一起来优化代码。
编写应用代码可以让您避免对计算之间的依赖关系做出不必要的声明——类似的单子代码会让您做出这样的声明。足够智能的库或编译器could原则上利用这一事实。
为了使这个想法更加具体,请考虑以下一元代码:
case class Foo(s: Symbol, n: Int)
val maybeFoo = for {
s <- maybeComputeS(whatever)
n <- maybeComputeN(whatever)
} yield Foo(s, n)
The for
- 理解脱糖或多或少类似于以下内容:
val maybeFoo = maybeComputeS(whatever).flatMap(
s => maybeComputeN(whatever).map(n => Foo(s, n))
)
我们知道maybeComputeN(whatever)
不依赖于s
(假设这些是行为良好的方法,不会在幕后改变某些可变状态),但编译器不会——从它的角度来看,它需要知道s
在开始计算之前n
.
应用版本(使用 Scalaz)如下所示:
val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))
在这里,我们明确指出这两个计算之间不存在依赖关系。
(是的,这个|@|
语法非常糟糕——参见这篇博文 http://meta.plasm.us/posts/2013/06/05/applicative-validation-syntax/进行一些讨论和替代方案。)
不过,最后一点确实是最重要的。挑选least能够解决你的问题的强大工具是一个非常强大的原则。有时你确实需要单子组合——在你的getPhoneByUserId
例如,方法,但通常你不这样做。
遗憾的是,Haskell 和 Scala 目前都使使用 monad 比使用应用函子方便得多(语法上等),但这主要是历史偶然的问题,并且像这样的发展成语括号 https://github.com/aztek/scala-workflow/是朝着正确方向迈出的一步。