何时以及为什么要在Scala中使用Applicative Functor
我知道Monad
可以在Scala中表示如下:
trait Monad[F[_]] { def flatMap[A, B](f: A => F[B]): F[A] => F[B] }
我明白为什么它是有用的。 例如,给出两个function:
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。 我猜想Option和List都是Applicatives
。 你能举一个简单的例子来说明如何使用Option和List来解释为什么我应该用它来代替 flatMap
吗?
引用自己 :
那么,为什么我们有单子的时候,为什么还要用到函数呢? 首先,我们不可能为我们想要的一些抽象提供monad实例,
Validation
就是一个很好的例子。其次(也是相关的),使用最强大的抽象技术来完成工作只是一个可靠的开发实践。 原则上这可能会允许优化,否则将是不可能的,但更重要的是,它使我们编写的代码更加可重用。
在第一段中扩展一下:有时你在单向代码和应用代码之间没有select。 查看答案的其余部分,以讨论为什么您可能希望使用Scalaz的Validation
(不能也不能有monad实例)来对validation进行build模。
关于优化点:在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
解释,或多或less地像下面这样:
val maybeFoo = maybeComputeS(whatever).flatMap( s => maybeComputeN(whatever).map(n => Foo(s, n)) )
我们知道maybeComputeN(whatever)
都不依赖于s
(假设这些方法都是在后台不改变某种可变状态的行为良好的方法),但编译器并不从它的angular度来看它需要知道s
它可以开始计算n
。
应用版本(使用Scalaz)如下所示:
val maybeFoo = (maybeComputeS(whatever) |@| maybeComputeN(whatever))(Foo(_, _))
这里我们明确指出两个计算之间没有依赖关系。
(是的,这个语法非常可怕 – 请参阅这篇博客文章,了解一些讨论和替代方法。)
但最后一点确实是最重要的。 select能够解决您的问题的最不强大的工具是一个非常强大的原则。 有时你确实需要getPhoneByUserId
组合 – 例如在你的getPhoneByUserId
方法中,但是通常你不需要。
可惜的是,Haskell和Scala目前使单子的工作变得比使用适用的函数更方便(语法等),但这主要是历史事件的问题,像成语括号这样的发展是正确的一步方向。
函子是为了提升计算到一个类别。
trait Functor[C[_]] { def map[A, B](f : A => B): C[A] => C[B] }
它对于一个variables的function来说是完美的。
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))
应用仿函数是将特殊值(类别中的值)应用于提升函数的仿函数。