由foldLeft错误混淆(在Eclipse和REPL中)

时间:2012-04-28 16:02:56

标签: scala

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

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

感谢您提供有关此事的任何见解。

3 个答案:

答案 0 :(得分:13)

Scala的匿名函数的占位符语法的局限性可能非常混乱(至少对我来说)。一条经验法则是下划线与最近的括号括起来,但这是一个近似值 - 详见the Scala specification的第6.23节:

  

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

在这种情况下,编译器不会将第二个下划线视为第二个参数。这可能看起来很奇怪,因为_ + _被恰当地视为具有两个参数,而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 object提供了这个名为identity的方法,我在这里使用它。不过,如果你发现更清楚的话,你可以轻松地写rowsByColumns.flatten.count(x => x)

答案 1 :(得分: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))

答案 2 :(得分:1)

  

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

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

  

推迟到未来发布

     

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

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

该提案最初于2011年提出,建议在ifforwhile循环中更改语法,将Scala的语法从Java和C语言中移除。
  虽然这种变化可能会更加美好,但委员会同意这样做会带来更多问题而不是利益。

请参阅issue 555