假设我需要编写一些函数来调用一些REST API:api1
,api2
,api3
。
def api1(url: Url) = ???
def api2(url: Url) = ???
def api3(url: Url) = ???
为简单起见,我使用自己的简化类Url
:
case class Url(host: String, port: Int, path: Path)
为了构建Url
我从配置和调用函数host
,port
,api1
中读取api2
和api3
,其中添加必需的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 => Url
和Option[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]
pure
从M[A => B]
A => B
Some
获取Option
这些操作应符合两个简单的法律,但这是另一个故事。
...
有意义吗?
答案 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。
在他们开始从“单子”到“验证应用程序”的过程之前,这是程序开始的方式:
检查一下,看看他们如何转换/改进代码(下载以获得最佳质量)。