我最难理解何时可以或不能省略括号和/或句点,以及它如何与_相互作用。
我的具体案例是
val x: X = ???
val xss: List[List[X]] = ???
xss map x :: _ //this doesn't compile
xss map _.::(x) //this is the same as the above (and thus doesn't compile)
以上两个似乎与xss.map(_).::(x)
相同
xss map (x :: _) //this works as expected
xss map {x :: _} //this does the same thing as the above
同时,以下也失败了:
xss.map xs => x :: xs //';' expected but '=>' found.
xss.map x :: _ //missing arguments for method map in class List; follow this method with `_' if you want to treat it as a partially applied function
//so when I try following the method with _, I get my favourite:
xss.map _ x :: _ //Cannot construct a collection of type That with elements of type B based on a collection of type List[List[Main.X]]
//as opposed to
xss map _ x :: _ //missing parameter type for expanded function ((x$1) => xss.map(x$1).x(($colon$colon: (() => <empty>))))
现在,我经常玩"切换符号直到它编译",我认为这是一种次优的编程策略。这一切是怎么回事?
首先我们需要区分xss.map(f)
和xss map f
。根据Scala文档,任何采用单个参数的方法都可以用作中缀运算符。
实际上List
中的map
方法就是其中之一。忽略完整签名及其从TraversableLike
继承的事实,签名如下:
final def map[B](f: (A) ⇒ B): List[B]
因此,它只需要一个参数,即f
,这是一个类型为A => B
的函数。因此,如果你有一个定义为的函数值
val mySize = (xs:List[Int]) => xs.size
您可以在之间进行选择
xss.map(mySize)
或
xss map mySize
这是一个偏好问题,但根据Scala风格指南,在这种情况下,后者是首选,除非它是复杂表达式的一部分,最好使用点表示法。
请注意,如果您选择使用点表示法,则始终需要用括号限定函数应用程序!这就是为什么以下编译都没有成功的原因。
xss.map xs => x :: xs // Won't compile
xss.map x :: _ // Won't compile
xss.map _ x :: _ // Won't compile
但大多数时候,您不需要传递函数值,而是需要传递函数文字(也称为匿名函数)。在这种情况下,如果使用点表示法,则需要类似xss.map(_.size)
的内容。但是如果你使用中缀表示法,这将是一个优先的问题。
例如
xss map x :: _ // Won't compile!
由于运算符优先级的原因,无法工作。因此,您需要使用括号来消除xss map (x :: _)
编译器的歧义。
使用大括号代替方括号有一个非常明确和简单的规则。同样,任何只带一个参数的函数都可以用大括号而不是方括号来应用,无论是中缀符号还是点符号。因此,将编译以下语句。
xss.map{x :: _}
xss map {x :: _}
为了避免混淆,您可以从点表示法和参数的显式类型开始。稍后,在编译之后——可能还为代码编写了一些单元测试——您可以开始重构代码,方法是删除不必要的类型,使用中缀表示法,并在有意义的地方使用大括号而不是方括号。
为此,您可以参考Scala风格指南和Martin Odersky在2013年Scala Days中关于Scala编码风格的演讲。此外,您还可以随时向IDE寻求帮助,以便将代码重构得更简洁。