2012-09-03 24 views
19

我有一个玩! 2 for Scala应用程序需要从外部服务中检索JSON格式的一些数据。Monad变换器是否适用于从服务获取JSON?

玩!框架允许通过将响应包装在Promise中来异步发出HTTP请求。 Promise是一个monad,它包含将来可用的值。

这很好,但在我的情况下,我从Web服务得到的是一个JSON字符串。我必须解析它,解析可能会失败。所以我必须把我得到的任何东西都包装进Option。结果是我的很多方法都返回Promise[Option[Whatever]]。也就是说,类型Whatever的值可能稍后可用。

现在每当我必须操作这样一个值,我需要map它两次。我想在下面的方式处理这个:

  • 创建一个新的类型,说Hope[A],一个包装了Promise[Option[A]]
  • 界定相关方法,如map(或者也许我应该使用foreach和继承一些集合特性?)和flatten
  • 提供Promise[Option[A]]Hope[A]之间的隐式转换器。

很容易界定map - 两个函子的成分又是一个函子 - 和flatten可以明确地在这种情况下进行,或每当组成一个单子有Option

但是这是我有限的理解,我不需要重新发明这个东西:monad变压器正好存在这种情况。或者说,好了,所以我想 - 我从来没有使用一个单子变压 - 这就是问题的要点:

能单子发电产品在这种情况下使用?我将如何去实际使用它们?

回答

15

使用Scalaz库的OptionT变压器,你应该能够将Promise[Option[A]]类型的值转换为类型OptionT[Promise, A]的值。

使用Scalaz 7:

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

要使用此值,例如打电话mapflatMap就可以了,你需要为PromiseFunctormapMonadflatMap)提供适当的类型类。

由于Promise是monadic,应该可以提供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 

要从OptionT[Promise, A]检索底层Promise[Option[A]]值,调用run方法。

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

您将获得使用单子变压器时,您可以撰写兼容类型的几种操作,保持操作之间的OptionT[Promise, _]型和检索末尾的潜在价值更大的效益。

为了在理解中编写操作,您需要使用类型为A => OptionT[Promise, B]的函数。

+0

谢谢,看来这正是我所需要的! – Andrea

+0

我已经尝试过了,在我的应用程序中一切正常。只有一件奇怪的事情:如果我引入一个类型别名'类型Hope [A] = Promise [Option [A]]'来简化函数的返回类型,我会得到一个编译时错误java.lang.IllegalArgumentException :转置要求所有集合具有相同的大小。你有什么线索为什么? – Andrea

+0

@Andrea:如果使用'optionT'显式类型参数,则错误应该消失。它看起来像一个编译器错误,如果您有时间可能值得检查[问题跟踪器](https://issues.scala-lang.org/secure/Dashboard.jspa)或在此处询问后续问题。 –

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

那么,在写'x map(_ * 2)'而不是你的四行。现在,在这个小例子中,它可能没有什么价值,但在更复杂的情况下,我认为它可能会使问题更加清楚。 – Andrea

+0

更新了我的回答 – drexin

+0

这个理解不会编译,因为'x'和'opt'不是兼容类型。您需要嵌套两个for-comprehensions。 –

相关问题