【问题标题】:When and why should one use Applicative Functors in Scala何时以及为什么应该在 Scala 中使用 Applicative Functor
【发布时间】:2013-11-21 17:04:24
【问题描述】:

我知道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]
}

我想知道什么时候应该使用它 而不是 monad 。我猜 Option 和 List 都是Applicatives。您能否举个简单的例子,将apply 与 Option 和 List 结合使用,并解释为什么我应该使用它而不是flatMap

【问题讨论】:

  • 检查“scalaz”exampletutorial examplescalaz tutorial
  • monad 不仅是一个带有flatMap 方法的特征,也是一个unit 方法以及一些必须验证的规律。
  • @YannMoisan 没错。为了简单起见,我省略了它们。
  • @GabrielRiba 我检查了 scalaz 示例。我看到他们如何使用<*>,但我仍然不明白为什么带有<*> 的示例比没有它的示例要好。所有的例子似乎都解决了这个问题。
  • 似乎 scalaz () 在右侧采用提升的功能,(与 Haskell 相反)但 () 是左关联的(右关联操作以 ':' 结尾),因此您需要将所有 () 项用右括号括起来。 (|@|) 的替代语法更简洁。

标签: scala functional-programming applicative


【解决方案1】:

quote myself

既然我们有 monad,为什么还要操心应用函子呢? 首先,根本不可能为 我们想要使用的一些抽象——Validation 是 完美的例子。

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

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

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

applicative 样式有时会导致代码更快,例如binary 将尝试通过将读取分组在一起来优化代码。

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

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

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

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

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

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 了解一些讨论和替代方案。)

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

很遗憾,目前 Haskell 和 Scala 都使得使用 monad 比使用 applicative functor 更方便(在语法等方面),但这主要是历史上的意外问题,像 idiom brackets 这样的发展是一个朝着正确的方向前进。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2010-12-07
      • 1970-01-01
      • 2014-05-22
      • 2013-05-10
      • 1970-01-01
      • 2023-03-08
      • 2013-01-29
      • 1970-01-01
      相关资源
      最近更新 更多