上下文非常简单。我的假设是基于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
我的问题是:为什么我收到错误并且不明确的情况,但是当我通过减少“简化”来映射函数时,它会正确执行?
感谢您提供有关此事的任何见解。
答案 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]
。然后,我们想知道该列表中有多少值是true
。 count
接受一个谓词并告诉您集合中有多少值满足该谓词。例如,这是计算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年提出,建议在
if
,for
和while
循环中更改语法,将Scala的语法从Java和C语言中移除。
虽然这种变化可能会更加美好,但委员会同意这样做会带来更多问题而不是利益。
请参阅issue 555