将许多Eithers映射到一个Either与许多

时间:2013-02-27 17:18:29

标签: scala monads

假设我有一个名为processOne的monadic函数,定义如下:

def processOne(input: Input): Either[ErrorType, Output] = ...

根据“Inputs”列表,我想返回Outputs中包含的“Either”的相应列表:

def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = ...

processMany会针对每个输入调用processOne,但是,我希望它在processOne返回Left时第一次终止(如果有的话),并返回Left,否则返回带有输出列表的Right

我的问题:实施processMany的最佳方式是什么?是否有可能使用for表达式来完成此行为,或者我是否需要以递归方式迭代列表?

3 个答案:

答案 0 :(得分:5)

使用Scalaz 7:

def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] =
  inputs.toStream traverseU processOne

inputs转换为Stream[Input]会利用traverse的非严格Stream实现,即为您提供所需的短路行为。

顺便说一句,你标记了这个“monads”,但是遍历只需要一个应用程序仿函数(事实上,它可能是根据Either的monad来定义的)。有关进一步参考,请参阅论文 The Essence of the Iterator Pattern ,或者,对于基于Scala的解释,Eric Torreborre关于该主题的blog post

答案 1 :(得分:2)

最简单的标准Scala,其评估不是必要的,可能是

def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = {
  Right(inputs.map{ x =>
    processOne(x) match {
      case Right(r) => r
      case Left(l) => return Left(l)
    }
  })
}

折叠会更紧凑,但是当它碰到左边时它不会短路(当你遍历整个输入时它只是继续携带)。

答案 2 :(得分:0)

现在,我决定使用递归解决这个问题,因为我不愿意为库添加依赖项(Scalaz)。

(我的应用程序中的类型和名称已在此处更改,以显示更通用)

def processMany(inputs: Seq[Input]): Either[ErrorType, Seq[Output]] = {
  import scala.annotation.tailrec

  @tailrec
  def traverse(acc: Vector[Output], inputs: List[Input]): Either[ErrorType, Seq[Output]]  = {
    inputs match {
      case Nil =>   Right(acc)
      case input :: more =>
          processOne(input) match {
            case Right(output) =>  traverse(acc :+ output, more)
            case Left(e) => Left(e)
          }
    }
  }

  traverse(Vector[Output](), inputs.toList)
}