在Scala中使用高阶函数时,命名参数与_,点表示法与中缀运算,花括号与圆括号



我最难理解何时可以或不能省略括号和/或句点,以及它如何与_相互作用。

我的具体案例是

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寻求帮助,以便将代码重构得更简洁。

最新更新