我有一个表示数据库记录的字符串ID列表。我想异步地从数据库中加载它们,然后异步上传每个记录到远程服务器,然后当完成所有记录上传后,记录上传的记录的ID。
由于我使用的是Scala 2.9.2,我使用的是Twitter的core-util Future实现,但它在Monadic转换方面应该与2.10期货完全一样。
一般概念是:
def fetch(id: String): Future[Option[Record]]
def upload(record: Record): Future[String]
def notifyUploaded(ids: Seq[String]): Unit
val ids: Seq[String] = ....
我试图通过for comprehension来做到这一点,但是fetch返回Future of Option这一事实使得它变得模糊不清并且代码无法编译:
for {
id <- ids
maybeRecord <- fetch(id)
record <- maybeRecord
uploadedId <- upload(record)
} yield uploadedId
编译此结果会导致以下错误:
scala: type mismatch;
found : com.twitter.util.Future[String]
required: Option[?]
uploadedId <- upload(record)
^
我错过了什么?为什么编译器期望uploadedId是一个选项?有什么好办法可以解决这个问题吗?
答案 0 :(得分:6)
考虑flatMap
(或绑定)函数的签名:
trait Monad[M[_]] {
def flatMap[A](a : M[A], f : A => M[B]) : M[B]
....
在您的情况下,您尝试在flatMap
上使用Option
,并为其生成f
,生成Future
。但正如在上面的签名中,f
应该在同一个monad中生成一些它被调用的东西。
Scala在这方面不一定非常有帮助,因为它非常善于将事物转换为(例如Seq
),这样你就会得到一种可以链接任意{{ 1}}一起调用,无论容器如何。
你可能需要的是一个'Monad变换器',它可以让你有一些组合monad的能力。 Debasish Ghosh有一篇关于使用Scalaz monad变换器的帖子here。
答案 1 :(得分:0)
你不能将所有不同类型混合在一起以便理解,我发现你可能会混合Seq和Option,结果将是Seq或Option,具体取决于第一个。无法混合Future和Seq或Option。如果你想使用for -reherehension,你将不得不将它们级联起来。在这种情况下,map / flatMap可能更好。我以两种方式实现了您的问题,并将类型添加到几个中间结果中,以便您在使用所有不同类型时看到正在创建的混乱。
object TestClass {
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
case class Record(id: String)
def fetch(id: String): Future[Option[Record]] = Future {
Thread.sleep(1000);
Some(Record(id))
}
def upload(record: Record): Future[String] = Future {
Thread.sleep(3000);
record.id + "_uploaded"
}
def notifyUploaded(ids: Seq[String]): Unit = println("notified" + ids)
val ids: Seq[String] = Seq("a", "b", "c")
def main(args: Array[String]): Unit = {
forComprehensionImpl()
mapAndFlatMapImpl()
}
def forComprehensionImpl() = {
val result: Seq[Future[Option[Future[String]]]] = for {
id <- ids
} yield {
for {
maybeRecord <- fetch(id)
} yield {
for {
record <- maybeRecord
} yield {
for {
uploadedId <- upload(record)
} yield uploadedId
}
}
}
val result2: Future[Seq[Option[Future[String]]]] = Future.sequence(result)
val result3: Future[Unit] = result2.flatMap { x: Seq[Option[Future[String]]] =>
Future.sequence(x.flatten).map(notifyUploaded)
}
Await.result(result3, Duration.Inf)
}
def mapAndFlatMapImpl() = {
val res: Seq[Future[Iterable[String]]] = ids.map { id =>
fetch(id).flatMap { maybeRecord =>
val res1: Option[Future[Seq[String]]] = maybeRecord.map { record =>
upload(record) map (Seq(_))
}
res1 match {
case Some(a) => a
case None => Future(Seq())
}
}
}
val res3: Future[Unit] = Future.sequence(res) map (a => notifyUploaded(a.flatten))
Await.result(res3, Duration.Inf)
}
}