【问题标题】:Confused by foldLeft error (both in Eclipse and REPL)对 foldLeft 错误感到困惑(在 Eclipse 和 REPL 中)
【发布时间】:2012-04-28 16:02:55
【问题描述】:

上下文非常简单。我的假设基于 Odersky 的书“Scala 编程,第 2 版”,第 8.5 节描述了“占位符语法”。

我有一个 List[List[Boolean]](即矩形位图),我试图在其中计算值“true”的总出现次数。这是定义执行良好的数据的 REPL 行:

val rowsByColumns =
    List(   List(false, true, false)
          , List(true, true, true)
          , List(false, true, false)
        )

接下来,我尝试使用以下行计算“true”的出现次数。而不是执行,我收到一个错误:

val marks = (for(row <- rowsByColumns)
    yield {row.foldLeft[Int](0)(_ + (if (_) 1 else 0))}).sum

<console>:8: error: wrong number of parameters; expected = 2
       val marks = (for(row <- rowsByColumns) yield {row.foldLeft[Int](0)(_ + (i
f (_) 1 else 0))}).sum
                                                                        ^

我不明白这个错误,因为我有两个下划线代表函数的参数。所以,我写了这个执行得很好,使函数更加明确:

val marks = (for(row <- rowsByColumns)
      yield {row.foldLeft[Int](0)((sum, marked) => sum + (if (marked) 1 else 0))}
    ).sum

我的问题是:为什么在不太明确的情况下我收到并出错,但是当我通过减少“简化”来绘制函数时,它会正确执行?

感谢您对此提供的任何见解。

【问题讨论】:

  • 进一步考虑这一点,Odersky 的提议(在某些 SIP 中)是否有可能改变 Scala 的语法以取消 if 评估表达式中所需的括号,从而纠正他的“缺陷”,使得通过从 if 中删除括号重新实现原始错误语句会使其正常工作吗?
  • 这是我所指的文档(SIP-12 Uncluttering Scala Syntax - Part 1: if):docs.scala-lang.org/sips/pending/uncluttering-control.html
  • 这两个语言设计问题(条件的括号和占位符语法的限制)是正交的——括号和下划线绑定之间的关系只是一个经验法则,你可以从像@这样的例子中看到987654325@,其中括号绑定下划线。

标签: scala


【解决方案1】:

Scala 对匿名函数的占位符语法的限制可能非常令人困惑(至少对我而言)。一个经验法则是,下划线绑定到它们最近的括号,但这是一个近似值 - 有关详细信息,请参阅 the Scala specification 的第 6.23 节:

句法类别Expr的表达式e 绑定下划线 u 部分,如果满足以下两个条件: (1) e 正确 包含 u,并且 (2) 没有其他句法表达 类别 Expr 正确包含在 e 中,并且它本身 正确包含 u

在这种情况下,编译器不会将第二个下划线视为第二个参数。这可能看起来很奇怪,因为_ + _ 被正确地视为有两个参数,而if (_) x else y 等效于z =&gt; if (z) x else y(其中z 是一个新的标识符),但是将两者嵌套是行不通的。

编译器确实可以在理论上找出两个下划线应该是foldLeft中同一个匿名函数的参数,但不是,例如,在下面,其中第二个下划线确实需要单独绑定:

rowsByColumns.map(_.map(!_))

不过,这需要编译器有很多额外的聪明才智,而且 Scala 语言设计者认为这是不值得的——只需要为一些没有嵌套表达式的相当简单的情况提供占位符语法。


幸运的是,在这种情况下,您可以改写 rowsByColumns.flatten.count(identity)flatten 在这里连接子列表以提供单个 List[Boolean]。然后我们想知道该列表中有多少值是truecount 接受一个谓词并告诉您集合中有多少值满足该谓词。例如,这里有一种计算 1 到 10(含)之间偶数的方法:

val isEven: Int => Boolean = _ % 2 == 0    
(1 to 10) count isEven

不过,在你的例子中,我们已经有了布尔值,所以谓词不需要做任何工作——它可以是标识函数x =&gt; x。正如 dhg 在评论中指出的那样,Scala 的 Predef object 提供了一个名为 identity 的方法,我在这里使用它。不过,如果你觉得更清楚,你也可以很容易地写 rowsByColumns.flatten.count(x =&gt; x)

【讨论】:

  • 与 dhg 的回复类似,我已经阅读并重新阅读了您的答案,但仍然不明白。我仍然没有看到错误解释是如何发生的,尽管我有一种模糊的感觉,即括号有两个含义;首先是数学意义(在一组运算符关系中设置优先级),其次是定义 Scala 函数字面量的方法。正如我对 dhg 所说,即使在这种情况下,为什么编译器不回溯错误并重新评估表达式以使其更有意义(对我而言,生成的函数文字是无意义的)。
  • 至于你的最后一句话——我不明白。我不知道什么是“身份”。我使用 Scalax.org 找到了很多对身份的崇敬,但不知道这意味着什么。你介意给我一个更英语的解释它的作用吗?我认为得到扁平部分,而不是计数或身份部分。
  • @chaotic3quilibrium, identity 只是一个预定义的函数,意思是x =&gt; x
  • @chaotic3quilibrium,我已经更新了答案。如果还不清楚,请告诉我。
  • @Travis 非常好。特维姆。至少我现在了解括号和占位符语法的冲突。
【解决方案2】:

我们可以通过查看一个更简单的案例来检查您的问题:

(0 to 1).map(x => if(x > 1) 1 else 0)  // fine
(0 to 1).map(if(_ > 1) 1 else 0)       // error

我们看到的错误是

<console>:8: error: missing parameter type for expanded function ((x$1) => x$1.$greater(1))
              (0 to 1).map(if(_ > 1) 1 else 0)
                              ^

所以发生的事情是 Scala 在尽可能窄的范围内将_ 扩展到(x$1) =&gt; x$1。换句话说,它正在尝试做:

(0 to 1).map(if((x) => x > 1) 1 else 0)

但这是错误的。

你的情况类似。这两个_ 不被视为都在同一个范围内,这就是为什么它认为只有一个参数。第二个_if 的范围内展开,这是错误的。它认为你正在这样做:

row.foldLeft[Int](0)((x) => x + (if ((y) => y) 1 else 0))

【讨论】:

  • 但请注意List(true, false).map(if (_) 1 else 0) 很好。问题不在于if 条件中的括号。
  • @dhg 即使在多次阅读您的答案之后,我仍然感到困惑。我没有看到 ...(if ((y) => y) 1 else 0)) 的扩展中的用途。即使它正在执行导致错误的扩展,为什么编译器不回溯以更一致地遵循表达式存在的任何地方的“引用透明度”概念的方式重新解释它,它可以替换为它的返回值?
【解决方案3】:

进一步考虑这一点,Odersky 的提议 (in SIP 12) 是否有可能改变 Scala 的语法以取消 if 评估表达式中所需的括号,从而纠正他的“缺陷”,从而重新实现原来的错误语句通过从 if 中删除括号将使其正常工作

注意(216 年 8 月,4 年后):决定是

推迟到未来版本

此 SIP 当前处于推迟状态,当前操作为 2.10。

  • (1) 弃用标识符中的“then”以保留将来的关键字状态。
  • (2) 弃用有问题的“while() do”语法。 (while 循环中的“do”循环需要大括号)。

最初于 2011 年提出,该提案建议在 ifforwhile 循环中更改语法,将 Scala 的语法从 Java 和类 C 语言中移开。
尽管这样的变化可以说是更美好,但委员会一致认为这会带来更多的问题而不是好处。

issue 555

【讨论】:

    猜你喜欢
    • 2023-04-08
    • 1970-01-01
    • 2013-06-02
    • 1970-01-01
    • 2021-11-28
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    相关资源
    最近更新 更多