Suppose I need to write some functions to invoke a few REST APIs: api1
, api2
, api3
.
def api1(url: Url) = ???
def api2(url: Url) = ???
def api3(url: Url) = ???
Suppose for simplicity that I use my own simplified class Url
:
case class Url(host: String, port: Int, path: Path)
In order to construct a Url
I read host
and port
from configuration and call functions api1
, api2
, api3
, which add required paths
and invoke their APIs:
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)
It is much better though to use a function Path => Url
(or builder pattern
if we write in Java
) in order to hide the host
and port
and other details of constructing Url
.
def api1(f: Path => Url) = ...
def api2(f: Path => Url) = ...
def api3(f: Path => Url) = ...
It is easy to implement such function f: Path => Url
with curring
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)
So far, so good but what if have optional host and port ?
val (hostOpt: Option[String], portOpt: Option[Int]) = ... // from configuration
Now we have a function String => Int => Path => Url
and Option[String]
and Option[Int]
. How to get Path => Url
?
Let's ask a slightly different question: How to get Option[Path => Url]
given String => Int => Path => Url
, Option[String]
, and Option[Int]
?
Fortunately we can define such an operation easily:
trait Option[A] { ... def ap[B](of: Option[A => B]): Option[B] = ??? }
Given this ap
we can answer the original question:
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))
Speaking abstractly, we have used the fact that Option
is an applicative functor. M
is an applicative functor if it is a functor and has two additional operations:
ap
to getM[B]
givenM[A => B]
andM[A]
pure
to getM[A => B]
fromA => B
(Some
forOption
)
These operations should comply with two simple laws but it's another story.
...
Does it make sense ?
case class Url(host: String, port: Int, path: Path); /* emulating config with _.some */ ("foo.com".some |@| 8080.some)(Function.uncurried(Url.curried))
scalaz
and itsApplicativeBuilder
is yet another story (or even two stories). BTW I am not sure yet I want to recommend theApplicativeBuilder
with its|@|
syntax.