【问题标题】:What about a Scala Future#collectWith method?Scala Future#collectWith 方法怎么样?
【发布时间】:2016-05-19 21:27:05
【问题描述】:

Scala Future 标准 API 中有 map/flatMap 方法,还有 recover/recoverWith 方法。

为什么没有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))
  }

感谢这篇文章,我知道我可以轻松实现它并“扩展”Future 标准 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)
  }

}

朱尔斯

【问题讨论】:

    标签: scala future standard-library


    【解决方案1】:

    怎么样:

    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 _ 返回一个失败的未来,这本质上是您在自定义 collectWith 实现中所做的。
    • 使用flatMap 而不是collectWith,因为handleResponse 现在适合该方法签名

    编辑以获取更多信息

    如果您确实需要支持PartialFunction 方法,您可以随时通过在部分函数上调用orElsePartialFunction[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 而不是你自己的自定义异常)

    【讨论】:

    • 我发现你的 flatMap 解决方案非常好,因为它很容易理解!谢谢 =) 并感谢有关将 PartialFunction 转换为函数的信息。我不知道这个技巧=)
    猜你喜欢
    • 2023-03-06
    • 1970-01-01
    • 2021-05-30
    • 2011-11-09
    • 1970-01-01
    • 1970-01-01
    • 2018-12-15
    • 2017-08-20
    • 2019-10-25
    相关资源
    最近更新 更多