何时在 Scala 中缀表示法中使用括号

Posted

技术标签:

【中文标题】何时在 Scala 中缀表示法中使用括号【英文标题】:When to use parenthesis in Scala infix notation 【发布时间】:2011-08-01 08:21:22 【问题描述】:

在使用 Scala 编程时,我会做越来越多的函数式工作。但是,在使用中缀表示法时,很难区分什么时候需要括号,什么时候不需要。

例如下面这段代码:

def caesar(k:Int)(c:Char) = c match 
    case c if c isLower => ('a'+((c-'a'+k)%26)).toChar
    case c if c isUpper => ('A'+((c-'A'+k)%26)).toChar
    case _ => c


def encrypt(file:String,k:Int) = (fromFile(file) mkString) map caesar(k)_

(fromFile(file) mkString) 需要括号才能编译。删除后出现以下错误:

Caesar.scala:24: error: not found: value map
    def encrypt(file:String,k:Int) = fromFile(file) mkString map caesar(k)_
                                                                 ^
one error found

mkString 显然返回一个字符串(通过隐式转换 AFAIK)我可以使用 map 函数。

为什么这种特殊情况需要括号?是否有关于何时以及为何需要它的一般准则?

【问题讨论】:

【参考方案1】:

这是我在阅读规范后为自己整理的:

任何采用单个参数的方法都可以用作中缀运算符:a.m(b) 可以写成a m b。 任何不需要参数的方法都可以用作后缀运算符:a.m 可以写成a m

比如a.##(b)可以写成a ## ba.!可以写成a!

后缀运算符的优先级低于中缀运算符,因此foo bar baz 表示foo.bar(baz)foo bar baz bam 表示(foo.bar(baz)).bamfoo bar baz bam bim 表示(foo.bar(baz)).bam(bim)。 同样给定对象 a 的无参数方法 ma.m.m 是有效的,但 a m m 不是因为它会解析为 exp1 op exp2

因为有一个版本的mkString 接受单个参数,所以在fromFile(file) mkString map caesar(k)_ 中将被视为中缀运算符。还有一个版本的mkString 不带参数,可以用作后缀运算符:

scala> List(1,2) mkString
res1: String = 12

scala> List(1,2) mkString "a"
res2: String = 1a2

有时通过在正确的位置添加点,您可以获得所需的优先级,例如fromFile(file).mkString map

所有这些优先级都发生在输入和其他阶段之前,所以即使list mkString map functionlist.mkString(map).function 一样没有意义,这就是它的解析方式。

【讨论】:

谢谢,这有助于澄清!【参考方案2】:

Scala reference 提及(6.12.3:前缀、中缀和后缀操作)

在一系列连续的类型中缀操作t0 op1 t1 op2 . . .opn tn 中,所有操作符op1, . . . , opn 必须具有相同的关联性。 如果它们都是左结合的,则序列被解释为(. . . (t0 op1 t1) op2 . . .) opn tn

在您的情况下,“map”不是运算符“mkstring”的术语,因此您需要分组(用括号括在“fromFile(file) mkString”周围)


其实Matt Rcmets:

这并不是真正的关联性问题,更多的是“后缀运算符的优先级始终低于中缀运算符。例如,e1 op1 e2 op2 始终等同于 (e1 op1 e2) op2”。 (同样来自 6.12.3)

huynhjl's answer (upvoted) 提供了更多细节,Mark Bush's answer (also upvoted) 指向“A Tour of Scala: Operators”以说明“任何采用单个参数的方法都可以用作中缀运算符”。

【讨论】:

so fromFile(file) mkString map caesar(k)_ 实际上是 op1(t0) op2 op3 t4 但它被解释为什么呢? @Felix:我怀疑op1(t0) op2(op3...)op3(即map)被错误地同化为术语而不是运算符。 我怀疑是这样的。如果您添加指向 scala 参考的链接,我将很高兴接受您的回答 :) @Felix:链接已添加,但您可能需要稍等,以防真正的 Scala 专家给出不同的解释;) 这并不是一个真正的关联性问题,更多的是“后缀运算符的优先级总是低于中缀运算符。例如 e1 op1 e2 op2 总是等价于 (e1 op1 e2) op2”。 (同样来自 6.12.3)【参考方案3】:

这里有一个简单的规则:永远不要使用后缀运算符。如果这样做,请将以后缀运算符结尾的整个表达式放在括号内。

事实上,从 Scala 2.10.0 开始,默认情况下这样做会产生警告。

为了更好地衡量,您可能希望将后缀运算符移出,并使用点表示法。例如:

(fromFile(file)).mkString map caesar(k)_

或者,更简单地说,

fromFile(file).mkString map caesar(k)_

另一方面,请注意可以提供空括号将它们变成中缀的方法:

fromFile(file) mkString () map caesar(k)_

【讨论】:

我不喜欢混合 . (点)带有中缀符号的符号。使用空括号将 mkString 转换为零元函数很有趣,但这需要我猜想用括号定义函数。 @Felix 实际上,我认为 Scala 2.8 上的空括号使用默认参数。 Daniel,我想我记得最近在 scala-lang 上看到过关于在未来版本中可能无法使用没有点的后缀参数的邮件。还是我产生了幻觉? @AmigoNico 你是对的。从 2.10 开始,这将是一个警告。我认为它永远不会被禁止,因为它对 DSL 非常有用,但它可能隐藏在新创建的 language 方案后面。 啊,谢谢——就是这样。并且为了其他读者的利益而纠正自己,我说的是后缀 parameters,但指的是后缀 operators。睡眠不足...【参考方案4】:

规范没有说清楚,但我的经验和实验表明,Scala 编译器总是会尝试将使用中缀表示法的方法调用视为中缀运算符。即使您对 mkString 的使用是后缀,编译器也会尝试将其解释为中缀,因此会尝试将“map”解释为其参数。后缀运算符的所有用法都必须紧跟表达式终止符,或者与“点”表示法一起使用,以便编译器能够看到它。

您可以在A Tour of Scala: Operators 中得到提示(尽管没有详细说明)。

【讨论】:

以上是关于何时在 Scala 中缀表示法中使用括号的主要内容,如果未能解决你的问题,请参考以下文章

Scala - 中缀与点符号

PTA-7-20 表达式转换(中缀转后缀,带括号,负数,小数转换)

7-20 表达式转换

第03次作业-栈和队列

5-20 表达式转换 (25分)

7-1 表达式转换 (25 分)