【问题标题】:Scala for comprehension of sequence inside a TryScala 用于理解 Try 中的序列
【发布时间】:2013-07-24 14:16:47
【问题描述】:

我正在编写一个 Scala 程序,其中有一个创建序列的操作。该操作可能会失败,因此我将其包含在 Try 中。我想在 for 理解中进行序列创建和枚举,以便成功创建的序列产生一个元组序列,其中第一个元素是序列,第二个是它的元素。

为了简化问题,将我的序列设为整数的Range,并定义一个createRange 函数,如果要求创建一个奇数长度的范围,该函数将失败。这是一个简单的理解,可以满足我的要求。

import scala.util.Try

def createRange(n: Int): Try[Range] = {
  Try {
    if (n % 2 == 1) throw new Exception
    else Range(0, n)
  }
}

def rangeElements(n: Int) {
  for {
    r <- createRange(n)
    x <- r
  } println(s"$r\t$x")
}

def main(args: Array[String]) {
  println("Range length 3")
  rangeElements(3)

  println("Range length 4")
  rangeElements(4)
}

如果你运行它,它会正确打印。

Range length 3
Range length 4
Range(0, 1, 2, 3)   0
Range(0, 1, 2, 3)   1
Range(0, 1, 2, 3)   2
Range(0, 1, 2, 3)   3

现在我想重写我的rangeElements 函数,以便它返回一个整数序列,而不是作为副作用打印,如果未创建范围,则该序列为空。我要写的是这个。

def rangeElements(n: Int):Seq[(Range,Int)] = {
  for {
    r <- createRange(n)
    x <- r
  } yield (r, x)
}
// rangeElements(3) returns an empty sequence
// rangeElements(4) returns the sequence (Range(0,1,2,3), 0), (Range(0,1,2,3), 1) etc.

这给了我两个类型不匹配的编译器错误。 r &lt;- createRange(n) 行需要 Seq[Int],但找到了 scala.util.Try[Nothing]x &lt;- r 行需要 scala.util.Try[?] 但找到 scala.collection.immutable.IndexedSeq[Int]

大概是 Try 的某种类型擦除让我很困惑,但我不知道它是什么。我在 for comprehension 中尝试了各种 toOptiontoSeq 限定词无济于事。

如果我只需要生成范围元素,我可以按照下面前两个答案的建议,自己显式处理createRangeSuccessFailure 条件。但是,我需要访问范围及其各个元素。

我意识到这是一个听起来很奇怪的例子。我要解决的真正问题是复杂的递归搜索,但我不想添加它的所有细节,因为这只会混淆这里的问题。

如何编写rangeElements 以产生所需的序列?

【问题讨论】:

    标签: scala exception for-comprehension


    【解决方案1】:

    Try 将是 SuccessRangen 是偶数或 FailureExceptionn 是奇数时。在rangeElements 中匹配并提取这些值。 Success 将包含有效的RangeFailure 将包含Exception。而不是返回Exception,而是返回一个空的Seq

    import scala.util.{Try, Success, Failure}
    
    def createRange(n: Int): Try[Range] = {
      Try {
        if (n % 2 == 1) throw new Exception
        else Range(0, n)
      }
    }
    
    def rangeElements(n: Int):Seq[Tuple2[Range, Int]] = createRange(n) match {
      case Success(s) => s.map(xs => (s, xs))
      case Failure(f) => Seq()
    }    
    
    scala> rangeElements(3)
    res35: Seq[(Range, Int)] = List()
    
    scala> rangeElements(4)
    res36: Seq[(Range, Int)] = Vector((Range(0, 1, 2, 3),0), (Range(0, 1, 2, 3),1), (Range(0, 1, 2, 3),2), (Range(0, 1, 2,3),3))
    

    【讨论】:

    • 这个答案是正确的。不幸的是,它让我意识到我原来的地址问题太简单了。我已将问题修改为更复杂的场景,更接近我要解决的实际问题。
    • case Success(s) =&gt; s 更改为此case Success(s) =&gt; s.map(xs =&gt; (s, xs)) 会得到输出Seq[(Range, Int)] = Vector((Range(0, 1, 2, 3),0), (Range(0, 1, 2, 3),1), (Range(0, 1, 2, 3),2), (Range(0, 1, 2, 3),3))
    • 我喜欢模式匹配解决方案。但是,for 理解完全没用。可以只使用匹配作为函数体...
    • 同意。从@W.P.McNeill 发布并与之合作的内容开始。我已经更新了答案。
    【解决方案2】:

    如果您将 for 理解转换为它的 map/flatMap 实现(如 Scala Language Spec 6.19 中所述),问题就会变得清晰。 flatMap 的结果类型为Try[U],但您的函数需要Seq[Int]

    for {
      r <- createRange(n)
      x <- r
    } yield x
    
    createRange(n).flatMap {
      case r => r.map {
        case x => x
      }
    }
    

    有什么理由不使用getOrElse 方法吗?

    def rangeElements(n: Int):Seq[Int] =
      createRange(n) getOrElse Seq.empty
    

    【讨论】:

    • 这个答案是正确的。不幸的是,它让我意识到我原来的地址问题太简单了。我已将问题修改为更复杂的场景,更接近我要解决的实际问题。
    • 在不了解实际问题的情况下很难找到正确的解决方案。在给定的情况下,您现在可以使用val r = createRange(n) getOrElse Range(0,0); r map { (r, _) } 之类的东西。我不确定您是否可以通过理解来实现目标。你可以在这里找到第二个解决方案scalakata.com/51f05d8ae4b0dcd4a06348da
    猜你喜欢
    • 2013-12-10
    • 2011-11-16
    • 1970-01-01
    • 2014-06-07
    • 1970-01-01
    • 2021-11-21
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多