1

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 get M[B] given M[A => B] and M[A]
  • pure to get M[A => B] from A => B (Some for Option)

These operations should comply with two simple laws but it's another story.

...

Does it make sense ?

2
  • 1
    That sounds right, but just for reference, you can simplify the syntax a bit. Using scalaz for applicative functors: case class Url(host: String, port: Int, path: Path); /* emulating config with _.some */ ("foo.com".some |@| 8080.some)(Function.uncurried(Url.curried))
    – Kolmar
    Commented Apr 16, 2015 at 22:44
  • Using scalaz and its ApplicativeBuilder is yet another story (or even two stories). BTW I am not sure yet I want to recommend the ApplicativeBuilder with its |@| syntax.
    – Michael
    Commented Apr 17, 2015 at 12:13

1 Answer 1

5

This sounds pretty reasonable to me, although I'm not sure if there's much of a question here, which is its own kind of problem.

I'm making this an answer instead of a comment because there's one thing worth noting. For many types there's a reason to avoid monadic binding and stick to ap beyond just "using less powerful abstractions is the right thing to do".

For example: the standard library future API's zip is an applicative operator that allows you to run futures in parallel, and if you use bar() zip foo() instead of for { f <- foo(); b <- bar() } yield (f, b) you can actually speed up your program (in many cases). For other types, using the applicative functor stuff instead of monadic bind offers other kinds of possibilities for optimization.

This isn't really the case with Option. It wouldn't be unreasonable to define ap in terms of flatMap. Using the applicative combinators is still "the right thing to do", but flatMap is right there and doesn't need extra definitions or dependencies and for-comprehensions are so simple and clean. There's just not the same kind of payoff that you see for things like futures.

1
  • But if you stick to the most general abstraction of your code, you might be able to replace Option by some applicative but non-mondic type-class in the future. Yes, it's not very likely...
    – ziggystar
    Commented Apr 17, 2015 at 6:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.