何时在 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 ## b
,a.!
可以写成a!
foo bar baz
表示foo.bar(baz)
而foo bar baz bam
表示(foo.bar(baz)).bam
和foo bar baz bam bim
表示(foo.bar(baz)).bam(bim)
。
同样给定对象 a 的无参数方法 m,a.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 function
与list.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 中缀表示法中使用括号的主要内容,如果未能解决你的问题,请参考以下文章