Do monad transformers apply to getting JSON from s

2019-03-14 14:13发布

问题:

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?

回答1:

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].



回答2:

-- 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]]