繁体   English   中英

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

[英]When and why should one use Applicative Functors in Scala

我知道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 with Option和List,并解释为什么我应该使用它而不是 flatMap

引用自己

那么,当我们有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)

for解决这个问题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(_, _))

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM