什么时候以及为什么应该在 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是单子:

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]
}

我不知道什么时候我应该使用它 而不是单子。我猜选项和列表都是 Applicatives。你能举个简单的例子说明如何使用 apply与选项和列表,并解释 为什么我应该使用它 而不是 flatMap

15606 次浏览

引用我自己的话:

既然我们有单子,为什么还要费心研究应用函子呢? 首先,不可能提供单子实例 我们需要处理的一些抽象ーー Validation是 完美的例子。

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

扩展一下第一段: 有时候在一元代码和应用程序代码之间没有选择。有关为什么要使用 Scalaz 的 Validation(它没有也不能有单子实例)进行建模的讨论,请参见 那个答案的其余部分 确认。

关于优化点: 这可能需要一段时间才能在 Scala 或 Scalaz 中得到普遍的应用,但是请参见例子 Haskell 的 Data.Binary文件:

应用程序样式有时会导致更快的代码,如 binary 将尝试通过将读取内容分组在一起来优化代码。

编写应用程序代码可以避免对计算之间的依赖性作出不必要的声明ーー类似的一元代码会让您作出这种声明。足够智能的库或编译器 可以原则上利用了这一点。

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

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——从编译器的角度来看,它需要了解 s,然后才能开始计算 n

应用程序版本(使用 Scalaz)如下所示:

val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))

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

(是的,这种 |@|语法非常糟糕ーー参见 这篇博文了解一些讨论和备选方案。)

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

遗憾的是,Haskell 和 Scala 现在使单子的工作比使用应用函数的工作更加方便(语法等) ,但这主要是历史的偶然,像 成语括号这样的开发是朝着正确的方向迈出的一步。

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

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))

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