【问题标题】:How to reduce Seq[Either[A,B]] to Either[A,Seq[B]]?如何将 Seq[Either[A,B]] 减少为 Either[A,Seq[B]]?
【发布时间】:2011-11-06 01:21:43
【问题描述】:

给定一系列 Seq[Either[String,A]]Left 是错误消息。如果序列的所有元素都是Right,我想在其中获得Right(将是Seq[A])获得Either[String,Seq[A]]。如果至少有一个Left(一条错误消息),我想获取第一条错误消息或所有错误消息的串联。

当然你可以发布 cat 或 scalaz 代码,但我也对不使用它的代码感兴趣。

编辑

我已更改标题,最初要求使用Either[Seq[A],Seq[B]] 来反映邮件的正文。

【问题讨论】:

    标签: scala functional-programming scalaz scala-cats


    【解决方案1】:

    它应该可以工作:

    def unfoldRes[A](x: Seq[Either[String, A]]) = x partition {_.isLeft} match {
      case (Nil, r) => Right(r map {_.right.get})
      case (l, _) => Left(l map {_.left.get} mkString "\n")
    }
    

    你把你的结果分成左右,如果左边是空的,建立一个右边,否则,建立一个左边。

    【讨论】:

      【解决方案2】:

      编辑:我错过了你的问题标题要求Either[Seq[A],Seq[B]],但我确实读过“我想获得第一条错误消息或所有错误消息的串联”,这会给你前者:

      def sequence[A, B](s: Seq[Either[A, B]]): Either[A, Seq[B]] =
        s.foldRight(Right(Nil): Either[A, List[B]]) {
          (e, acc) => for (xs <- acc.right; x <- e.right) yield x :: xs
        }
      
      scala> sequence(List(Right(1), Right(2), Right(3)))
      res2: Either[Nothing,Seq[Int]] = Right(List(1, 2, 3))
      
      scala> sequence(List(Right(1), Left("error"), Right(3)))
      res3: Either[java.lang.String,Seq[Int]] = Left(error)
      

      使用 Scalaz:

      val xs: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
      
      scala> xs.sequenceU
      res0:  scala.util.Either[String,List[Int]] = Right(List(1, 2, 3))
      

      【讨论】:

      • 这比我使用匹配的解决方案要短。我认为您可以使用 for-comprehension 使折叠功能更具可读性。
      • 是的,您可以将foldLeft 中的函数体替换为:for (xs &lt;- acc.right; x &lt;- e.right) yield x :: xs
      • 如果顺序很重要,可能直接构建 Seq 会更容易(并使用:+
      • sequence(Seq(Left("a"), Left("b"))) 返回Left("b") 而不是Left("a"),这将是第一个失败。因此,这与原始规范不完全匹配。
      【解决方案3】:

      我不习惯使用 Either - 这是我的方法;也许有更优雅的解决方案:

      def condense [A] (sesa: Seq [Either [String, A]]): Either [String, Seq [A]] = {
        val l = sesa.find (e => e.isLeft)
        if (l == None) Right (sesa.map (e => e.right.get)) 
        else Left (l.get.left.get)
      }
      
      condense (List (Right (3), Right (4), Left ("missing"), Right (2)))
      // Either[String,Seq[Int]] = Left(missing)
      condense (List (Right (3), Right (4), Right (1), Right (2)))
      // Either[String,Seq[Int]] = Right(List(3, 4, 1, 2))
      

      Left (l.get.left.get) 看起来有点搞笑,但l 本身是一个 Either [A, B],而不是一个 Either [A, Seq[B]],需要重新包装。

      【讨论】:

      • 你不依赖于打字——当心打字员!
      • 我的意思是你在第 2 行断言一个条件,你在第 3 行隐式使用它,在那里你写了'e.right.get'。例如。接受的答案不会离开“打字的安全性”。
      • 我不确定我所说的是否有道理:在第二行中,您断言如果lNone 则没有错误。然后,您使用这些知识(在您的脑海中)编写.get,其中您知道它永远不会失败,尽管.get 可以抛出。如果您查看 Ben James 的回答,他从不调用可能失败的方法。
      • 重点是,例如Ben 得到了同样的结果,他只调用了永远不会失败的方法。是的,你的 get 调用永远不会失败,因为你已经以一种可行的方式构建了算法。但是 Ben 的方法永远不会失败,因为他从不调用可能失败(抛出)的方法。他的代码是正确的(以某种方式),这是由编译器断言的。您的代码是正确的,这是通过您构建算法的方式来断言的。您可以否定“if”的条件(错误地),它仍然可以编译但不起作用。
      • 所以我的代码的问题是,如果我编写不同的代码,它可能会失败,而如果 Ben 编写不同的代码,它就不会编译?如果您对可能发生的事情和可能发生的事情感到满意……如果我会归还大象,我会归还大象。我不反对 Bens 的解决方案。
      【解决方案4】:

      给定一个起始序列xs,这是我的看法:

      xs collectFirst { case x@Left(_) => x } getOrElse
        Right(xs collect {case Right(x) => x})
      

      这是对问题主体的回答,仅获得第一个错误为Either[String,Seq[A]]。这显然不是标题中问题的有效答案


      返回所有错误:

      val lefts = xs collect {case Left(x) => x }
      def rights = xs collect {case Right(x) => x}
      if(lefts.isEmpty) Right(rights) else Left(lefts)
      

      请注意,rights 被定义为一种方法,因此仅在需要时才对其进行评估

      【讨论】:

      • 不需要downvote,但第二种解决方案可能会导致列表的2次遍历,而您可以在一次遍历中将左右分开
      • @Nicolas:没错,但这是一个公平的交易,清晰与过早优化。这种性质的问题很少会达到足够大的规模,以至于性能影响会很明显。
      【解决方案5】:

      以 Kevin 的解决方案为基础,并从 Haskell 的 Either 类型中窃取一点,您可以像这样创建一个方法 partitionEithers:

      def partitionEithers[A, B](es: Seq[Either[A, B]]): (Seq[A], Seq[B]) =
        es.foldRight (Seq.empty[A], Seq.empty[B]) { case (e, (as, bs)) =>
          e.fold (a => (a +: as, bs), b => (as, b +: bs))
        }
      

      并使用它来构建您的解决方案

      def unroll[A, B](es: Seq[Either[A, B]]): Either[Seq[A], Seq[B]] = {
        val (as, bs) = partitionEithers(es)
        if (!as.isEmpty) Left(as) else Right(bs)
      }
      

      【讨论】:

        【解决方案6】:

        这里是scalaz代码:

        _.sequence

        【讨论】:

        • 这是一个非对称函数,如 OP 中所述(将左侧与右侧区别对待)!?因为没有对混合序列的规范处理。如果是这样,实现的选择是否由左边通常是错误消息这一事实来定义?
        • 是的,这是典型的例子,也是 OP 描述的例子。另一种实现将错误值与半组连接。
        • 如果没有明确地向sequence 提供类型参数,我无法让它工作(使用 6.0.4-SNAPSHOT)。否则,我会得到:Cannot prove that Either[String,Int] &lt;:&lt; N[B].
        • 嗨,Ben,您可能需要一些类型注释。对不起,我把它关掉了。如果你不能开始,我会在 REPL 上给你一个更具体的例子。
        • 顺便说一下,我已经就这个功能进行了 1 小时的演讲。我有幻灯片,但没有视频。我不确定幻灯片会有多大帮助,但这里是dl.dropbox.com/u/7810909/docs/applicative-errors-scala/…
        【解决方案7】:

        我的回答与@Garrett Rowe 的类似:但它使用 foldLeft(另见:Why foldRight and reduceRight are NOT tail recursive?)并预先添加到 Seq 而不是附加到 Seq(参见:Why is appending to a list bad?)。

        scala> :paste
        // Entering paste mode (ctrl-D to finish)
        
        def partitionEitherSeq[A,B](eitherSeq: Seq[Either[A,B]]): (Seq[A], Seq[B]) =
          eitherSeq.foldLeft(Seq.empty[A], Seq.empty[B]) { (acc, next) =>
          val (lefts, rights) = acc
          next.fold(error => (lefts :+ error, rights), result => (lefts, rights :+ result))
        }
        
        // Exiting paste mode, now interpreting.
        
        partitionEitherSeq: [A, B](eitherSeq: Seq[Either[A,B]])(Seq[A], Seq[B])
        
        scala> partitionEitherSeq(Seq(Right("Result1"), Left("Error1"), Right("Result2"), Right("Result3"), Left("Error2")))
        res0: (Seq[java.lang.String], Seq[java.lang.String]) = (List(Error1, Error2),List(Result1, Result2, Result3))
        

        【讨论】:

          【解决方案8】:

          Scala 2.13 开始,大多数集合都提供了partitionMap 方法,该方法基于将项目映射到RightLeft 的函数来划分元素。

          在我们的例子中,我们甚至不需要将输入转换为RightLeft 的函数来定义分区,因为我们已经有了Rights 和Lefts。因此简单地使用identity!

          那么这只是一个根据是否有左来匹配得到的左和右分区元组的问题:

          eithers.partitionMap(identity) match {
            case (Nil, rights)       => Right(rights)
            case (firstLeft :: _, _) => Left(firstLeft)
          }
          
          // * val eithers: List[Either[String, Int]] = List(Right(1), Right(2), Right(3))
          //         => Either[String,List[Int]] = Right(List(1, 2, 3))
          // * val eithers: List[Either[String, Int]] = List(Right(1), Left("error1"), Right(3), Left("error2"))
          //         => Either[String,List[Int]] = Left("error1")
          

          中间步骤详情(partitionMap):

          List(Right(1), Left("error1"), Right(3), Left("error2")).partitionMap(identity)
          // => (List[String], List[Int]) = (List("error1", "error2"), List(1, 3))
          

          【讨论】:

            猜你喜欢
            • 2012-10-18
            • 2019-09-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-10-23
            • 2015-12-08
            • 1970-01-01
            • 2012-10-11
            相关资源
            最近更新 更多