假设我有一个名为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
表达式来完成此行为,或者我是否需要以递归方式迭代列表?
答案 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)
}