18

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?

4

2 回答 2

14

使用 Scalaz 库的OptionT转换器,您应该能够将 typePromise[Option[A]]的值转换为 type 的值OptionT[Promise, A]

使用 Scalaz 7:

import scalaz.OptionT._
val x: OptionT[Promise, Int] = optionT(Promise.pure(Some(123)))

要使用此值,例如调用map或对其进行调用,您需要为( for , for )flatMap提供适当的类型类。PromiseFunctormapMonadflatMap

由于Promise是一元的,因此应该可以提供Monad[Promise]. (您将免费获得FunctorApplicative,因为类型类形成了继承层次结构。)例如(注意:我没有测试过这个!):

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
}

举个简单的例子,你现在可以mapOptionT[Promise, A], 上使用一个类型的函数A => B到里面的值:

def foo[A, B](x: OptionT[Promise, A], f: A => B): OptionT[Promise, B] = x map f

要从 检索基础Promise[Option[A]]OptionT[Promise, A],请调用该run方法。

def bar[A, B](x: Promise[Option[A]], f: A => B): Promise[Option[B]] =
  optionT(x).map(f).run

OptionT[Promise, _]当您可以组合多个兼容类型的操作、保留操作之间的类型并在最后检索基础值时,您将从使用 monad 转换器中获得更多好处。

要在理解中组合操作,您将需要 type 的函数A => OptionT[Promise, B]

于 2012-09-03T13:13:39.943 回答
1

-- 删除 --

编辑:

好的,您可以简单地使用scalaz.OptionT这里:

val x = optionT(Promise { /* api call */ some("""{ "foo": "bar" }""") })
val mapped = x.map(Json.parse).run // run return the resulting Promise[Option[T]]
于 2012-09-03T12:23:00.807 回答