Scala - 中缀符号与点符号表达式

41

对于这两者,是否有最佳实践?我一直在阅读 Odersky 等人的 Scala 书籍,看起来中缀符号用于许多集合 API 函数,而点号则保留给程序员定义的函数。

4个回答

55

我个人没有任何硬性规定,但我倾向于仅在符号方法名称中使用中缀表示法,并在字母数字方法名称中使用点表示法。

中缀表示法使修改代码变得繁琐。以下是一些例子。

想象你有这行代码:

xs filter { f } map { g }

假设在以后的某个时间点,您需要在末尾添加一个 toList。可以这样做:


xs filter { f } map { g } toList
这可能会导致分号推断问题。为了避免这些问题,您可以在结尾处放置一个分号或者换行符。在我看来,这两个选项都很丑陋。为了避免所有这些无聊的问题,我更喜欢使用xs.filter(f).map(g)。使用这种语法重构始终更容易。
另一个例子:假设我在代码中有以下内容:
if(foo contains bar) { ..

比如说,我需要否定这个条件。如果我将它修改为以下内容:

if(!foo contains bar) { ..

糟糕,这会被解析为(!foo).contains(bar)。这不是我们想要的。

或者假设您需要增加一个新条件,并且您修改它如下:

if(foo contains bar && cond) { ..

又是个糟糕的问题,这被解析成了foo.contains(bar.&&(cond))。这还不是我们想要的。

当然,你可以添加一堆括号来解决,但与点表示法相比,这样做既难看又难以阅读/编辑。

现在,我上面所说的所有内容都适用于符号方法名。但是,使用点语法时,符号方法看起来很不自然,因此我更喜欢对它们使用中缀语法。


上述指南的一个例外:内部DSL。它们通常经过精心设计,以便在按照其文档/示例中所规定的方式(通常使用中缀符号)编写时不会引起解析问题。


7
相同的逻辑也可以支持中缀符号表示法,因为它提供了一个额外的机制来控制优先级(除了选择操作符)。并且它使优先级更加显而易见。例如,尝试使用真实函数而不仅仅是抬升方法来重写您的第一个示例:(xs filter f map g).toList 仍然比 xs.filter(f).map(g).toList 更清晰、更明显。 - Kevin Wright
2
在中缀表示法中,加上括号需要进行非线性编辑,这是避免使用中缀表示法的另一个原因。去掉大括号也无济于事。 - missingfaktor
2
省略括号正是重点所在。当与运算符的相对“强度”和括号/大括号的使用相结合时,能够同时使用中缀和点表示法来控制优先级,这是保持代码清晰的非常有力的工具。 - Kevin Wright
3
someExpression => someExpression.newPart(someExpression).newPart之间的区别不显著。在两种情况下,都必须添加.newPart。诚然,在第二种情况下需要添加括号,但你很可能一开始在第一种情况下就用了更少的括号。将“这可能会导致分号推断问题”的解释用作支持前一种方法的草人论法是错误的——这是添加后缀调用的一个症状,而不是原始表达式的问题。 - Kevin Wright
1
@missingfaktor,我已经开始避免使用这种中缀调用,正是因为你所描述的原因。 - Jean-Philippe Pellet
显示剩余3条评论

12

这是个人喜好问题。你使用其中一种风格的决定应该基于哪种风格可以使你的代码最容易阅读。

但请注意,省略点和括号的能力仅限于某些句法结构,因此有时你只能使用它们。


6
请注意,a.op(b)a op b长但比(a op b)短。这是一个细微的差别,但它可以成为“最易读”的考虑因素之一。 - Rex Kerr
如果你需要节省每一个字节,你可以使用a?b代替a.?(b)或a ? b。当然,像这样命名的方法数量是有限制的。 :) - user unknown

4
在官方 Scala 网站文档中有一个很好的样式指南,描述了中缀符号法与点符号法的正确用法。
后缀表示法:
names.toList
// is the same as
names toList // Unsafe, don't use!

参数个数为1:

// right!
names foreach (n => println(n))
names mkString ","
optStr getOrElse "<empty>"
// wrong!
javaList add item

高阶函数:

// wrong!
names.map (_.toUpperCase).filter (_.length > 5)
// right!
names map (_.toUpperCase) filter (_.length > 5)

符号方法/运算符:

// right!
"daniel" + " " + "spiewak"
// wrong!
"daniel"+" "+"spiewak"

0

我发现在使用cats库创建笛卡尔积时,使用中缀符号表示map非常方便。例如:

(fetchIt(1) |@| fetchIt(2) |@| fetchIt(3)).map(MyCaseClass)

你可以这样去掉周围的括号:

fetchIt(1) |@| fetchIt(2) |@| fetchIf(3) map MyCaseClass

在我看来,第二种变体读起来更好。我想这只是个人口味问题。只是想添砖加瓦。

上述代码之所以有效,是因为"|@|"中的|比"map"中的m具有更高的优先级。请阅读Scala语言规范的这一部分以了解更多细节:

如果表达式中有几个中缀操作符,则具有较高优先级的运算符比具有较低优先级的运算符更紧密地绑定。

http://scala-lang.org/files/archive/spec/2.12/06-expressions.html#infix-operations


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接