72

我知道Monad可以用 Scala 表示如下:

trait Monad[F[_]] {
  def flatMap[A, B](f: A => F[B]): F[A] => F[B]
}

我明白它为什么有用了。例如,给定两个函数:

getUserById(userId: Int): Option[User] = ...
getPhone(user: User): Option[Phone] = ...

我可以很容易地编写函数getPhoneByUserId(userId: Int),因为Option它是一个 monad:

def getPhoneByUserId(userId: Int): Option[Phone] = 
  getUserById(userId).flatMap(user => getPhone(user))

...

现在我Applicative Functor在 Scala 中看到:

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
}

我想知道什么时候应该使用它而不是monad 。我猜 Option 和 List 都是Applicatives. 您能否给出使用applywith Option 和 List 的简单示例,并解释为什么我应该使用它而不是 flatMap

4

2 回答 2

81

引用我自己的话:

那么,当我们有 monad 时,为什么还要操心应用函子呢?首先,根本不可能为我们想要使用的一些抽象提供 monad 实例——<code>Validation 就是一个完美的例子。

其次(也是相关的),使用最不强大的抽象来完成工作只是一种可靠的开发实践。原则上,这可能会实现原本不可能的优化,但更重要的是,它使我们编写的代码更具可重用性。

扩展一下第一段:有时您无法在单子代码和应用代码之间进行选择。请参阅该答案的其余部分,以讨论为什么您可能想要使用 Scalaz Validation(它没有也不能有 monad 实例)来进行模型验证。

关于优化点:这可能需要一段时间才能在 Scala 或 Scalaz 中普遍相关,但请参阅Haskell 的文档Data.Binary

applicative 风格有时会产生更快的代码,因为它binary 会尝试通过将读取分组在一起来优化代码。

编写应用程序代码可以避免对计算之间的依赖关系做出不必要的声明——类似的单子代码会要求你这样做。一个足够聪明的库或编译器原则上可以利用这一事实。

为了使这个想法更具体一点,请考虑以下一元代码:

case class Foo(s: Symbol, n: Int)

val maybeFoo = for {
  s <- maybeComputeS(whatever)
  n <- maybeComputeN(whatever)
} yield Foo(s, n)

-comprehensionfor脱糖或多或少类似于以下内容:

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(_, _))

在这里,我们明确指出两个计算之间没有依赖关系。

(是的,这种|@|语法非常糟糕——有关一些讨论和替代方法,请参阅此博客文章。)

不过,最后一点确实是最重要的。选择最强大的工具来解决你的问题是一个非常强大的原则。有时你确实需要一元组合——getPhoneByUserId例如在你的方法中——但通常你不需要。

遗憾的是,Haskell 和 Scala 目前都使使用 monad 比使用 applicative 函子更方便(在语法等方面),但这主要是历史上的偶然问题,像成语括号这样的发展是正确的一步方向。

于 2013-11-09T18:57:12.900 回答
28

Functor 用于将计算提升到一个类别。

trait Functor[C[_]] {
  def map[A, B](f : A => B): C[A] => C[B]
}

它非常适用于一个变量的函数。

val f = (x : Int) => x + 1

但是对于 2 及以上的函数,在提升到一个类别之后,我们有以下签名:

val g = (x: Int) => (y: Int) => x + y
Option(5) map g // Option[Int => Int]

它是应用函子的签名。并且要将以下值应用于函数g——需要一个应用函子。

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
} 

最后:

(Applicative[Option] apply (Functor[Option] map g)(Option(5)))(Option(10))

Applicative functor 是将特殊值(类别中的值)应用于提升函数的函子。

于 2013-11-09T20:05:07.070 回答