8

上下文非常简单。我的假设基于 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

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

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

4

3 回答 3

13

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

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

在这种情况下,编译器不会将第二个下划线视为第二个参数。这可能看起来很奇怪,因为_ + _它被正确地视为具有两个参数,并且if (_) x else y等效于z => 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 => x。正如 dhg 在评论中指出的那样,Scala 的Predef对象将其作为名为 的方法提供identity,我在这里使用它。rowsByColumns.flatten.count(x => x)但是,如果您发现它更清晰,您也可以轻松编写。

于 2012-04-28T16:41:54.170 回答
3

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

(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) => 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))
于 2012-04-28T16:33:10.637 回答
1

考虑到这一点,Odersky 的提议(在 SIP 12 中)是否有可能改变 Scala 的语法以取消 if 评估表达式中所需的括号,从而纠正他的“缺陷”,以便通过删除if 中的括号将使其正常工作

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

推迟到未来版本

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

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

该提案最初于 2011 年提出,建议对if'sforwhileloops 进行语法更改,将 Scala 的语法从 Java 和类 C 语言中移开。
尽管这样的变化可以说是更美好,但委员会同意这会带来更多的问题而不是好处。

问题 555

于 2016-08-21T12:54:54.127 回答