除非找到特定值,否则如何创建将值传递给内部Iteratee的Iteratee

时间:2015-11-25 12:50:01

标签: scala playframework iterate

我有一个ADT,它基本上是OptionTry之间的交叉:

sealed trait Result[+T]
case object Empty extends Result[Nothing]
case class Error(cause: Throwable) extends Result[Nothing]
case class Success[T](value: T) extends Result[T]

(假设常见的组合器如mapflatMap等在结果上定义

如果Iteratee[A, Result[B]名为inner,我想创建一个具有以下行为的新Iteratee[Result[A], Result[B]]

  • 如果输入为Success(a),请将a提供给inner
  • 如果输入为Empty,则为no-op
  • 如果输入为Error(err),我希望inner完全被忽略,而是返回Done次迭代,其结果为Error(err)

示例行为:

// inner: Iteratee[Int, Result[List[Int]]]
// inputs:
1
2
3
// output:
Success(List(1,2,3))

// wrapForResultInput(inner): Iteratee[Result[Int], Result[List[Int]]]
// inputs:
Success(1)
Success(2)
Error(Exception("uh oh"))
Success(3)

// output:
Error(Exception("uh oh"))

这听起来像是Enumeratee的工作,但是我无法在the docs中找到任何看起来像我想要的东西,内部实现是还是巫毒的。

如何实现wrapForResultInput来创建上述行为?

添加一些不太适合评论的细节:

是的,我的问题看起来好像错了。我用Iteratees来描述它,但似乎我真的在寻找Enumeratees

在我正在构建的API的某个点上,有一个Transformer[A]类,基本上是Enumeratee[Event, Result[A]]。我想允许客户通过提供Enumeratee[Result[A], Result[B]]来转换该对象,这会产生Transformer[B]又称Enumeratee[Event, Result[B]]

对于更复杂的示例,假设我有一个Transformer[AorB],并希望将其转换为Transformer[(A, List[B])]

// the Transformer[AorB] would give
a, b, a, b, b, b, a, a, b

// but the client wants to have
a -> List(b),
a -> List(b, b, b),
a -> Nil
a -> List(b)

客户端可以使用Enumeratee[AorB, Result[(A, List[B])]]实现Enumeratee.grouped而不会有太多麻烦,但是他们需要提供Enumeratee[Result[AorB], Result[(A, List[B])],这似乎会引入很多我想要的复杂功能如果可能,请隐藏起来。

val easyClientEnumeratee = Enumeratee.grouped[AorB]{
  for {
    _ <- Enumeratee.dropWhile(_ != a) ><> Iteratee.ignore
    headResult <- Iteratee.head.map{ Result.fromOption }
    bs <- Enumeratee.takeWhile(_ == b) ><> Iteratee.getChunks
} yield headResult.map{_ -> bs}

val harderEnumeratee = ??? ><> easyClientEnumeratee

val oldTransformer: Transformer[AorB] = ... // assume it already exists
val newTransformer: Transformer[(A, List[B])] = oldTransformer.andThen(harderEnumeratee)

所以我要找的是???来定义harderEnumeratee,以减轻已经实施easyClientEnumeratee的用户的负担。

我想???应该是Enumeratee[Result[AorB], AorB],但如果我尝试类似

的话
Enumeratee.collect[Result[AorB]] {
  case Success(ab) => ab
  case Error(err) => throw err
}

实际上会抛出错误;我实际上希望错误以Error(err)的形式返回。

1 个答案:

答案 0 :(得分:1)

最简单的实现方法是Iteratee.fold2方法,可以收集元素直到发生某些事情。

由于您返回单个结果并且在确认没有错误之前无法真正返回任何内容,Iteratee就足以完成此类任务

def listResults[E] = Iteratee.fold2[Result[E], Either[Throwable, List[E]]](Right(Nil)) { (state, elem) =>
  val Right(list) = state
  val next = elem match {
    case Empty => (Right(list), false)
    case Success(x) => (Right(x :: list), false)
    case Error(t) => (Left(t), true)
  }
  Future(next)
} map {
  case Right(list) => Success(list.reverse)
  case Left(th) => Error(th)
}

现在,如果我们准备小游乐场

import scala.concurrent.ExecutionContext.Implicits._
import scala.concurrent.{Await, Future}
import scala.concurrent.duration._

val good = Enumerator.enumerate[Result[Int]](
  Seq(Success(1), Empty, Success(2), Success(3)))

val bad = Enumerator.enumerate[Result[Int]](
  Seq(Success(1), Success(2), Error(new Exception("uh oh")), Success(3)))

def runRes[X](e: Enumerator[Result[X]]) : Result[List[X]] = Await.result(e.run(listResults), 3 seconds)

我们可以验证这些结果

runRes(good) //res0: Result[List[Int]] = Success(List(1, 2, 3))
runRes(bad) //res1: Result[List[Int]] = Error(java.lang.Exception: uh oh)