【问题标题】:Strange (?) for comprehension evaluation in ScalaScala中的理解评估奇怪(?)
【发布时间】:2014-02-11 07:39:56
【问题描述】:

现在,我花了 一段时间 才弄清楚为什么我的递归会以某种方式破坏堆栈。这是导致此问题的部分:

scala> for {
     |   i <- List(1, 2, 3)
     |   j = { println("why am I evaluated?"); 10 } if false
     | } yield (i, j)
why am I evaluated?
why am I evaluated?
why am I evaluated?
res0: List[(Int, Int)] = List()

这不是疯了吗?如果j = ...if false 结尾,那么为什么要评估它永远不会被使用?

当你有一个递归调用而不是{ println ... } 时会发生什么(以及递归保护而不是if false),我了解到。 :

为什么?!

【问题讨论】:

  • 出于好奇,您是否来自 Python 并希望这与他们的内联 if 表达式一样?
  • @nafg,一点也不。 =) 我从来没有用 Python 写过一行代码。

标签: scala recursion for-comprehension


【解决方案1】:

我会冒险说接受的答案可以说得更多。

这是一个解析器错误。

守卫可以立即跟随生成器,但除此之外,semi 是必需的(实际的或推断的)。

Here is the syntax.

在下面,res4 的行不应该编译。

scala> for (i <- (1 to 5).toList ; j = 2 * i if j > 4) yield j
res4: List[Int] = List(6, 8, 10)

scala> for (i <- (1 to 5).toList ; j = 2 * i ; if j > 4) yield j
res5: List[Int] = List(6, 8, 10)

发生的情况是 j 的 val def 与 i 生成器合并以生成对 (i,j) 的新生成器。然后守卫看起来就像它只是跟随(合成)生成器。

但语法仍然是错误的。语法是我们的​​朋友!早在类型系统之前,它就是我们的 BFF。

res5 的行上,很明显守卫不守卫val def。

更新:

实施错误已降级(或升级,取决于您的观点)为specification bug

检查这种用法,如果控制它前面的 valdef,就像在 Perl 中一样,守卫看起来像一个尾随,属于你最喜欢的样式检查器的权限。

【讨论】:

  • +1。我们应该填写错误报告吗?无论如何,接受的答案向我展示了如何使它以某种优雅的方式工作:只需重新排序 for 中的表达式。
  • @MichałRus 我刚刚提交了它。不管你接受什么答案;你永远不会回到你生命中的那个时刻。但你的困惑并没有错。
【解决方案2】:

如果你像这样构造你的循环,它将解决你的问题:

scala> for {
     |   i <- List(1, 2, 3)
     |   if false
     |   j = { println("why am I evaluated?"); 10 }
     | } yield (i, j)
res0: List[(Int, Int)] = List()

for 循环中的 Scala 语法将 if 语句视为一种过滤器; this tutorial 有一些很好的例子。

一种思考方式是强制遍历 for 循环,当您到达 if 语句时,如果该语句的计算结果为 false,您将继续循环的下一次迭代。

【讨论】:

    【解决方案3】:

    当我有这样的问题时,我想看看反汇编代码的样子(例如,将 .class 文件提供给 JD-GUI)。

    这个便于理解的反汇编代码的开头是这样的:

    ((TraversableLike)List..MODULE$.apply(Predef..MODULE$.wrapIntArray(new int[] { 1, 2, 3 })).map(new AbstractFunction1() { public static final long serialVersionUID = 0L;
    
          public final Tuple2<Object, BoxedUnit> apply(int i) { Predef..MODULE$.println("why am I evaluated?"); BoxedUnit j = BoxedUnit.UNIT;
    
            return new Tuple2(BoxesRunTime.boxToInteger(i), 
              j);
          }
        }...//continues
    

    我们可以看到 i 参数中的 int 数组映射到 AbstractFunction1(),其 apply 方法首先执行 println nomatter what 然后将 Unit 分配给参数j 最后返回一个二元组(i,j),以进一步将其通过管道传递到进一步的过滤器/映射操作(省略)。所以基本上if false 条件没有任何影响,基本上被编译器删除了。

    【讨论】:

    • 也感谢您的洞察力。 :)
    • 生态系统中的工具是 scalac -Xprint:typer 和 REPL 中的 :javap(通常的 javap)。
    猜你喜欢
    • 2012-06-15
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 2010-11-14
    • 2018-07-06
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    相关资源
    最近更新 更多