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

Mic*_*ael 65 functional-programming scala applicative

我知道Monad可以在Scala中表达如下:

trait Monad[F[_]] {
  def flatMap[A, B](f: A => F[B]): F[A] => F[B]
}
Run Code Online (Sandbox Code Playgroud)

我明白为什么它有用.例如,给定两个功能:

getUserById(userId: Int): Option[User] = ...
getPhone(user: User): Option[Phone] = ...
Run Code Online (Sandbox Code Playgroud)

我可以轻松编写函数,getPhoneByUserId(userId: Int)因为它Option是一个monad:

def getPhoneByUserId(userId: Int): Option[Phone] = 
  getUserById(userId).flatMap(user => getPhone(user))
Run Code Online (Sandbox Code Playgroud)

...

现在我Applicative Functor在Scala中看到:

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
}
Run Code Online (Sandbox Code Playgroud)

我想知道何时应该使用它而不是 monad.我猜选项和列表都是Applicatives.你能给出使用applyOption和List的简单例子,并解释为什么我应该使用它而不是 flatMap

Tra*_*own 77

引用自己:

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

第二个(相关地),使用功能最弱的抽象来完成工作只是一个可靠的开发实践.原则上,这可能允许优化,否则这是不可能的,但更重要的是它使我们编写的代码更可重用.

要扩展第一段:有时您无法在monadic和applicative代码之间进行选择.请参阅该答案的其余部分,以讨论为什么您可能希望使用Scalaz Validation(不具备且不能具有monad实例)来模拟验证.

关于优化点:它可能还需要一段时间才能在Scala或Scalaz中普遍相关,但请参阅Haskell的文档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)
Run Code Online (Sandbox Code Playgroud)

for-comprehension desugars的东西或多或少这样的:

val maybeFoo = maybeComputeS(whatever).flatMap(
  s => maybeComputeN(whatever).map(n => Foo(s, n))
)
Run Code Online (Sandbox Code Playgroud)

我们知道这maybeComputeN(whatever)不依赖于s(假设这些是行为良好的方法,并没有改变幕后的某些可变状态),但是编译器没有 - 从它的角度来看它需要知道s它才能开始计算n.

应用版本(使用Scalaz)看起来像这样:

val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))
Run Code Online (Sandbox Code Playgroud)

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

(是的,这种|@|语法非常糟糕 - 请参阅此博客文章,了解一些讨论和替代方案.)

不过,最后一点确实是最重要的.选择能够解决问题的最不强大的工具是一个非常强大的原则.有时你确实需要monadic组合 - getPhoneByUserId例如你的方法 - 但通常你不需要.

令人遗憾的是,Haskell和Scala目前使用monad工作比使用applicative functors更方便(语法等),但这主要是历史事故的问题,而成语括号的发展是右边的一步方向.


Yur*_*riy 24

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

trait Functor[C[_]] {
  def map[A, B](f : A => B): C[A] => C[B]
}
Run Code Online (Sandbox Code Playgroud)

它适用于一个变量的功能.

val f = (x : Int) => x + 1
Run Code Online (Sandbox Code Playgroud)

但是对于2和更多的函数,在提升到类别后,我们有以下签名:

val g = (x: Int) => (y: Int) => x + y
Option(5) map g // Option[Int => Int]
Run Code Online (Sandbox Code Playgroud)

它是应用程序仿函数的签名.并将以下值应用于函数g- 需要一个aplicative functor.

trait Applicative[F[_]] {
  def apply[A, B](f: F[A => B]): F[A] => F[B]
} 
Run Code Online (Sandbox Code Playgroud)

最后:

(Applicative[Option] apply (Functor[Option] map g)(Option(5)))(Option(10))
Run Code Online (Sandbox Code Playgroud)

Applicative functor是一个仿函数,用于将特殊值(类别中的值)应用于提升函数.