2

假设我在调用中processOne定义了一个单子函数,如下所示:

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

给定一个“ Inputs”列表,我想返回一个Outputs包含在一个“”中的相应列表Either

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

processMany将调用processOne它具有的每个输入,但是,我希望它终止第一次(如果有)processOne返回 aLeft并返回 that Left,否则返回 aRight和输出列表。

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

4

3 回答 3

5

使用Scalaz 7:

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

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

顺便说一句,您标记了这个“monads”,但遍历只需要一个应用函子(碰巧,它可能是根据 monad 定义的Either)。如需进一步参考,请参阅论文The Essence of the Iterator Pattern,或者,对于基于 Scala 的解释,请参阅 Eric Torreborre 关于该主题的博客文章

于 2013-02-27T17:44:31.587 回答
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)
    }
  })
}

折叠会更紧凑,但当它撞到左边时不会短路(它只会在你迭代整个输入时继续携带它)。

于 2013-02-27T17:30:11.470 回答
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)
}
于 2013-02-27T19:20:17.180 回答