了解Scala中的Applicative Functor

时间:2015-04-16 20:59:37

标签: scala functional-programming functor applicative

假设我需要编写一些函数来调用一些REST API:api1api2api3

def api1(url: Url) = ???
def api2(url: Url) = ???
def api3(url: Url) = ???

为简单起见,我使用自己的简化类Url

case class Url(host: String, port: Int, path: Path)  

为了构建Url我从配置和调用函数hostportapi1中读取api2api3,其中添加必需的paths并调用其API:

def api1(host: String, port: Int) = ???
def api2(host: String, port: Int) = ???
def api3(host: String, port: Int) = ???

val (host, port) = ... // read from the configuration

// call the APIs
api1(host, port) 
api2(host, port)
api3(host, port)

使用函数Path => Url(或builder pattern如果我们写入Java)以便隐藏 {{1} }和host以及构建port的其他详细信息。

Url

使用 curring

可以轻松实现此类功能def api1(f: Path => Url) = ... def api2(f: Path => Url) = ... def api3(f: Path => Url) = ...
f: Path => Url

到目前为止,非常好,但如果有可选主机和端口呢?

val url: String => Int => Path = (Url.apply _).curried
val (host, port) = ... // from the configuration
val f = url(host, port)
api1(f)
api2(f)
api3(f)

现在我们有一个功能val (hostOpt: Option[String], portOpt: Option[Int]) = ... // from configuration String => Int => Path => Url以及Option[String]。如何获得Option[Int]

让我们问一个稍微不同的问题:如何Path => Url Option[Path => Url]String => Int => Path => UrlOption[String]

幸运的是,我们可以轻松定义这样的操作:

Option[Int]

鉴于此trait Option[A] { ... def ap[B](of: Option[A => B]): Option[B] = ??? } ,我们可以回答原始问题:

ap

抽象地说,我们使用 val of: Option[Path => Url] = portOpt ap (hostOpt ap Some(url) of.map(f => api1(f)) of.map(f => api2(f)) of.map(f => api3(f)) applicative functor 的事实。如果Option是一个仿函数并且还有两个附加操作,那么M是一个应用仿函数:

  • ap获取M[B] M[A => B]M[A]
  • pureM[A => B] A => B Some获取Option

这些操作应符合两个简单的法律,但这是另一个故事。

...

有意义吗?

2 个答案:

答案 0 :(得分:4)

这对我来说听起来很合理,虽然我不确定这里是否有很多问题,这是它自己的问题。

我这是一个答案,而不是评论,因为有一件事值得注意。对于许多类型而言,有理由避免使用monadic绑定并坚持ap而不仅仅是“使用不那么强大的抽象是正确的事情”。

例如:标准库未来API的zip是一个应用运算符,允许您并行运行期货,如果使用bar() zip foo()代替for { f <- foo(); b <- bar() } yield (f, b),您实际上可以加速你的程序(在很多情况下)。对于其他类型,使用applicative functor而不是monadic bind提供了其他类型的优化可能性。

Option的情况并非如此。用ap来定义flatMap是不合理的。使用应用程序组合器仍然是“正确的做法”,但flatMap就在那里,不需要额外的定义或依赖,for - 理解是如此简单和干净。对于诸如期货之类的东西,你所看到的收益并不相同。

答案 1 :(得分:1)

朱莉·莫罗诺基(Julie Moronuki)和克里斯·马丁(Chris Martin)在他们的《用Haskell寻找成功(和失败)》一书中,有一个很好的例子来理解Applicative和Monad之间的区别-我发现它很有用,因此我将以下幻灯片作为基础: https://www.slideshare.net/pjschwarz/applicative-functor-part-2

在其中,我将正在运行的示例转换为Scala。

在他们开始从“单子”到“验证应用程序”的过程之前,这是程序开始的方式:

enter image description here

检查一下,看看他们如何转换/改进代码(下载以获得最佳质量)。