【问题标题】:Mapping many Eithers to one Either with many将多个 Either 映射到一个具有多个的 Either
【发布时间】:2013-02-28 03:20:17
【问题描述】:

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

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 表达式来完成此行为,或者我是否有必要自己递归地迭代列表?

【问题讨论】:

    标签: scala monads


    【解决方案1】:

    Scalaz7:

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

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

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

    【讨论】:

    • Views 和流一样有效,对吧?使用.view 而不是.toSteram
    • 这将需要一个 Traverse[SeqView] 实例,我认为 scalaz 中没有现成可用的实例。
    【解决方案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)
        }
      })
    }
    

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

    【讨论】:

      【解决方案3】:

      目前,我决定只使用递归来解决这个问题,因为我不愿意向库 (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)
      }
      

      【讨论】:

      • 虽然这会起作用,但我不太明白为什么你更喜欢这个,例如我发布的解决方案。除非您使用通常有错误的小列表,否则您的列表会更长且更慢。
      • 我无法让您的解决方案通过类型检查器。左派如何向上传播?
      • 我的错;对于那个很抱歉。我第一次忽略了“返回”。你的答案有效。
      猜你喜欢
      • 2015-12-08
      • 1970-01-01
      • 2020-08-21
      • 2019-02-14
      • 1970-01-01
      • 1970-01-01
      • 2020-09-08
      • 1970-01-01
      • 2020-03-26
      相关资源
      最近更新 更多