I have a Play! 2 for Scala application that needs to retrieve some data in JSON format from an external service.
The Play! framework allows to make HTTP requests asynchronously by wrapping the response in a Promise. Promise
is a monad that wraps a value that will be available in the future.
This is fine, but in my case what I get from the web service is a JSON string. I have to parse it and the parsing might fail. So I have to wrap whatever I get into an Option
. The result is that many of my methods are returning Promise[Option[Whatever]]
. That is, a value of type Whatever
that will be, maybe, available later.
Now whenever I have to operate over such a value I need to map
it twice. I was thinking of handling this in the following way:
- creating a new type, say
Hope[A]
, that wraps a Promise[Option[A]]
- defining the relevant methods like
map
(or maybe I should use foreach
and inherit from some collection trait?) and flatten
- provide an implicit converter between
Promise[Option[A]]
and Hope[A]
.
It is easy to define map
- the composition of two functors is again a functor - and flatten
can be done explicitly in this case, or whenever composing a monad with Option
.
But it is my limited understanding that I do not need to reinvent this stuff: monad transformer exist for exactly this case. Or, well, so I think - I have never used a monad tranformer - and this is the point of the question:
Can monad tranformers be used in this situation? How would I go about actually using them?
Using the Scalaz library's OptionT
transformer, you should be able to turn values of type Promise[Option[A]]
into values of type OptionT[Promise, A]
.
Using Scalaz 7:
import scalaz.OptionT._
val x: OptionT[Promise, Int] = optionT(Promise.pure(Some(123)))
To use this value, for example to call map
or flatMap
on it, you will need to provide an appropriate typeclass for Promise
(Functor
for map
, Monad
for flatMap
).
Since Promise
is monadic, it should be possible to provide an instance of Monad[Promise]
. (You'll get Functor
and Applicative
for free, because the typeclasses form an inheritance hierarchy.) For example (note: I've not tested this!):
implicit val promiseMonad = new Monad[Promise] {
def point[A](a: => A): Promise[A] = Promise.pure(a)
def bind[A, B](fa: Promise[A])(f: A => Promise[B]): Promise[B] = fa flatMap f
}
As a simple example, you can now use map
on the OptionT[Promise, A]
, to apply a function of type A => B
to the value inside:
def foo[A, B](x: OptionT[Promise, A], f: A => B): OptionT[Promise, B] = x map f
To retrieve the underlying Promise[Option[A]]
value from an OptionT[Promise, A]
, call the run
method.
def bar[A, B](x: Promise[Option[A]], f: A => B): Promise[Option[B]] =
optionT(x).map(f).run
You will gain more benefit from using monad transformers when you can compose several operations of compatible types, preserving the OptionT[Promise, _]
type between operations and retrieving the underlying value at the end.
To compose operations in a for-comprehension, you will need functions of type A => OptionT[Promise, B]
.
-- removed --
edit:
Okay, you can simply use the scalaz.OptionT
here:
val x = optionT(Promise { /* api call */ some("""{ "foo": "bar" }""") })
val mapped = x.map(Json.parse).run // run return the resulting Promise[Option[T]]