Scala Future#collectWith 方法怎么样?
What about a Scala Future#collectWith method?
有 map
/flatMap
方法,Scala Future 标准中还有 recover
/recoverWith
方法 API。
为什么没有 collectWith
?
collect
方法的代码非常简单:
def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] =
map {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
collectWith
方法的代码就很容易想象了:
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
我知道我可以实现它和 "extend" 未来标准 API 很容易感谢这篇文章:http://debasishg.blogspot.fr/2008/02/why-i-like-scalas-lexically-scoped-open.html
我在我的项目中做到了:
class RichFuture[T](future: Future[T]) {
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
future.flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
}
trait WithRichFuture {
implicit def enrichFuture[T](person: Future[T]): RichFuture[T] = new RichFuture(person)
}
也许我的需求不足以在标准中实现它 API ?
这就是我在 Play2 项目中需要此方法的原因:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse: PartialFunction[WSResponse, Future[Car]] = {
case response: WSResponse if response.status == Status.CREATED => Future.successful(response.json.as[Car])
case response: WSResponse
if response.status == Status.BAD_REQUEST && response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
}
// The "carApiClient.createCar" method just returns the result of the WS API call.
carApiClient.createCar(key, eligibleCars.head).collectWith(handleResponse)
}
如果没有我的 collectWith
方法,我不知道该怎么做。
也许这样做不是正确的方法?
你知道更好的方法吗?
编辑:
对于不需要 collectWith
方法的 createCar
方法,我可能有更好的解决方案:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
for {
mayCar: Option[Car] <- Future.successful(eligibleCars.headOption)
r: WSResponse <- carApiClient.createCar(key, mayCar.get) if mayCar.nonEmpty
createdCar: Car <- Future.successful(r.json.as[Car]) if r.status == Status.CREATED
createdCar: Car <- createCar(key, eligibleCars.tail) if r.status == Status.BAD_REQUEST && r.json.as[Error].error == "not_the_good_one"
} yield createdCar
}
您如何看待第二种解决方案?
第二次编辑:
仅供参考,感谢@Dylan 的回答,这是我的最终解决方案:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def doCall(head: Car, tail: List[Car]): Future[Car] = {
carApiClient
.createCar(key, head)
.flatMap( response =>
response.status match {
case Status.CREATED => Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, tail)
}
)
}
eligibleCars match {
case head :: tail => doCall(head, tail)
case Nil => Future.failed(new RuntimeException)
}
}
朱尔斯
怎么样:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse(response: WSResponse): Future[Car] = response.status match {
case Status.Created =>
Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
case _ =>
// just fallback to a failed future rather than having a `collectWith`
Future.failed(new NoSuchElementException("your error here"))
}
// using flatMap since `handleResponse` is now a regular function
carApiClient.createCar(key, eligibleCars.head).flatMap(handleResponse)
}
两个变化:
handleResponse
不再是偏函数。 case _
returns 一个失败的未来,这基本上就是您在自定义 collectWith
实现中所做的。
- 使用
flatMap
而不是 collectWith
,因为 handleResponse
现在适合该方法签名
编辑以获取更多信息
如果你真的需要支持 PartialFunction
方法,你总是可以通过在部分函数上调用 orElse
将 PartialFunction[A, Future[B]]
转换为 Function[A, Future[B]]
,例如
val handleResponsePF: PartialFunction[WSResponse, Future[Car]] = {
case ....
}
val handleResponse: Function[WSResponse, Future[Car]] = handleResponsePF orElse {
case _ => Future.failed(new NoSucheElementException("default case"))
}
这样做可以让您调整现有的部分函数以适应 flatMap
调用。
(好吧,从技术上讲,它已经这样做了,但你会抛出 MatchErrors 而不是你自己的自定义异常)
有 map
/flatMap
方法,Scala Future 标准中还有 recover
/recoverWith
方法 API。
为什么没有 collectWith
?
collect
方法的代码非常简单:
def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] =
map {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
collectWith
方法的代码就很容易想象了:
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
我知道我可以实现它和 "extend" 未来标准 API 很容易感谢这篇文章:http://debasishg.blogspot.fr/2008/02/why-i-like-scalas-lexically-scoped-open.html
我在我的项目中做到了:
class RichFuture[T](future: Future[T]) {
def collectWith[S](pf: PartialFunction[T, Future[S]])(implicit executor: ExecutionContext): Future[S] =
future.flatMap {
r => pf.applyOrElse(r, (t: T) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t))
}
}
trait WithRichFuture {
implicit def enrichFuture[T](person: Future[T]): RichFuture[T] = new RichFuture(person)
}
也许我的需求不足以在标准中实现它 API ?
这就是我在 Play2 项目中需要此方法的原因:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse: PartialFunction[WSResponse, Future[Car]] = {
case response: WSResponse if response.status == Status.CREATED => Future.successful(response.json.as[Car])
case response: WSResponse
if response.status == Status.BAD_REQUEST && response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
}
// The "carApiClient.createCar" method just returns the result of the WS API call.
carApiClient.createCar(key, eligibleCars.head).collectWith(handleResponse)
}
如果没有我的 collectWith
方法,我不知道该怎么做。
也许这样做不是正确的方法?
你知道更好的方法吗?
编辑:
对于不需要 collectWith
方法的 createCar
方法,我可能有更好的解决方案:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
for {
mayCar: Option[Car] <- Future.successful(eligibleCars.headOption)
r: WSResponse <- carApiClient.createCar(key, mayCar.get) if mayCar.nonEmpty
createdCar: Car <- Future.successful(r.json.as[Car]) if r.status == Status.CREATED
createdCar: Car <- createCar(key, eligibleCars.tail) if r.status == Status.BAD_REQUEST && r.json.as[Error].error == "not_the_good_one"
} yield createdCar
}
您如何看待第二种解决方案?
第二次编辑:
仅供参考,感谢@Dylan 的回答,这是我的最终解决方案:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def doCall(head: Car, tail: List[Car]): Future[Car] = {
carApiClient
.createCar(key, head)
.flatMap( response =>
response.status match {
case Status.CREATED => Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, tail)
}
)
}
eligibleCars match {
case head :: tail => doCall(head, tail)
case Nil => Future.failed(new RuntimeException)
}
}
朱尔斯
怎么样:
def createCar(key: String, eligibleCars: List[Car]): Future[Car] = {
def handleResponse(response: WSResponse): Future[Car] = response.status match {
case Status.Created =>
Future.successful(response.json.as[Car])
case Status.BAD_REQUEST if response.json.as[Error].error == "not_the_good_one" =>
createCar(key, eligibleCars.tail)
case _ =>
// just fallback to a failed future rather than having a `collectWith`
Future.failed(new NoSuchElementException("your error here"))
}
// using flatMap since `handleResponse` is now a regular function
carApiClient.createCar(key, eligibleCars.head).flatMap(handleResponse)
}
两个变化:
handleResponse
不再是偏函数。case _
returns 一个失败的未来,这基本上就是您在自定义collectWith
实现中所做的。- 使用
flatMap
而不是collectWith
,因为handleResponse
现在适合该方法签名
编辑以获取更多信息
如果你真的需要支持 PartialFunction
方法,你总是可以通过在部分函数上调用 orElse
将 PartialFunction[A, Future[B]]
转换为 Function[A, Future[B]]
,例如
val handleResponsePF: PartialFunction[WSResponse, Future[Car]] = {
case ....
}
val handleResponse: Function[WSResponse, Future[Car]] = handleResponsePF orElse {
case _ => Future.failed(new NoSucheElementException("default case"))
}
这样做可以让您调整现有的部分函数以适应 flatMap
调用。
(好吧,从技术上讲,它已经这样做了,但你会抛出 MatchErrors 而不是你自己的自定义异常)