何时以及为什么要在Scala中使用Applicative Functors

时间:2013-11-09 17:59:43

标签: scala functional-programming applicative

我知道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))

...

现在我在Scala中看到Applicative Functor

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

我想知道何时应该使用代替 monad。我猜选项和列表都是Applicatives。您是否可以举例说明如何将apply与选项和列表一起使用,并解释为什么我应该使用而不是 flatMap

2 个答案:

答案 0 :(得分:77)

quote myself

  

那么,当我们有monad时,为什么还要使用applicative functor呢?   首先,根本不可能为monad实例提供   我们想要使用的一些抽象 - Validation就是   完美的例子。

     

第二(以及相关),它只是一个可靠的开发实践   能够完成工作的最不强大的抽象。在   原则这可能允许优化,否则将是   可能,但更重要的是它使我们编写的代码更多   可重复使用的。

要在第一段上展开一点:有时您无法在monadic和applicative代码之间进行选择。请参阅that answer的其余部分,以了解为什么您可能希望使用Scalaz的Validation(它没有且不能有monad实例)来建模 验证

关于优化点:它可能还需要一段时间才能在Scala或Scalaz中普遍相关,但请参见例如the documentation for Haskell's Data.Binary

  

应用样式有时会导致更快的代码,binary   将尝试通过将读取分组来优化代码。

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

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

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

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

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

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

(是的,这个|@|语法非常糟糕 - 请参阅this blog post进行一些讨论和替代方案。)

但最后一点确实是最重要的。选择最少强大的工具来解决您的问题是一个非常强大的原则。有时你真的需要monadic composition - 例如你的getPhoneByUserId方法 - 但通常你没有。

令人遗憾的是,Haskell和Scala目前使用monad工作比使用applicative functors更方便(语法等),但这主要是历史事故和像idiom brackets这样的发展。是朝着正确方向迈出的一步。

答案 1 :(得分:23)

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 - 需要一个aplicative functor。

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是一个函数,用于将特殊值(类别中的值)应用于提升函数。