了解Scala中的中缀方法调用和cons运算符(::)

21

我对Scala编程语言相当新,并且在按照这里的讲座笔记时,遇到了一些困惑。

我认为我并没有真正理解cons运算符的工作原理,下面是我尝试过的一些东西:

我创建了一个伪随机数生成器,然后尝试创建一个包含一个随机值的列表:

scala> val gen = new java.util.Random
gen: java.util.Random = java.util.Random@1b27332

scala> gen nextInt 3 :: Nil
<console>:7: error: type mismatch;
 found   : List[Int]
 required: Int
       gen nextInt 3 :: Nil
                     ^

但它试图将 List(3) 传递给 nextnt 方法。当我使用括号时,就没有问题了。

scala> (gen nextInt 3) :: Nil
res69: List[Int] = List(1)

我对执行顺序很好奇,所以我创建了一个函数来检查它。

scala> def pr(i:Int):Int = { println(i); i }
pr: (i: Int)Int

scala> pr(1) :: pr(2) :: pr(3) :: Nil
1
2
3
res71: List[Int] = List(1, 2, 3)

如输出中所示,执行顺序与出现顺序相同。那么我想可能是关于 'nextInt' 函数的问题,然后我尝试了以下操作:
scala> 1 + 2 :: Nil
res72: List[Int] = List(3)

它首先执行了加法,之后才执行cons。那么问题来了:gen nextInt 3 :: Nil1 + 2 :: Nil有什么区别?
2个回答

44

这里有两个需要关注的问题:运算优先级结合性。就像sepp2k提到的那样,Stack Overflow上的这个问题解释了优先级规则,但是引用的规则并不完整,而且从Scala 2.7到Scala 2.8也有很小的变化。不过它们主要涉及以=结尾的操作符。

至于结合性,Scala中几乎所有的东西都是从左到右读取的,这是程序员所习惯的。然而,在Scala中,以:结尾的操作符是从右到左读取的。

接下来以这个例子为例:

1 + 2 :: Nil

首先,是优先级。 +: 哪个优先级更高?根据表格,+ 优先于 :,因此先执行加法。因此,表达式等于这个值:

((1).+(2)) :: Nil

现在没有优先级冲突,但由于:::结尾,它有一个不同的固定性。它从右到左读取,因此:

Nil.::((1).+(2))

另一方面,在这个例子中:

gen nextInt 3 :: Nil

运算符::的优先级高于nextInt,因为:的优先级高于所有字母。因此,需要记住它的固定性,变成:

gen nextInt Nil.::(3)

然后它就变成了

gen.nextInt(Nil.::(3))

在哪个时刻错误就会变得明显。

PS: 我写的是(1).+(2)而不是1.+(2),因为在本文撰写时,1.被解释为双精度数,使得将double 1.0加到2中成为一个中缀表达式。这种语法已经从Scala 2.10.0开始被弃用,并且可能不会在Scala 2.11中存在。


谢谢您详细的回答,它给了我许多关于其他事情的线索。但是我还有一个问题:编译器如何解释我在初始问题中提供的第三个代码部分中的表达式;从括号的角度来看呢?因为使用函数pr显示项按从左到右的顺序执行。 - ciuncan
1
正确的,函数调用是从左到右进行评估的。这是因为pr(X)::的参数表达式,这些表达式按照出现顺序先进行评估,然后将值传递给方法。pr(1) :: pr(2) :: pr(3) :: Nil输出 1 2 3。但是,Nil.::(pr(3)).::(pr(2)).::(pr(1))输出 3 2 1。但是两者都返回了 List(1, 2, 3) - michael.kebe
1
@DanielC.Sobral 在你的例子中,点号前面不应该有空格吗?它不应该是:(1 .+(2)) :: Nil 否则你会将1转换为双精度浮点数。 - dublintech
@dublintech 确实,已经修复了。不过下一个主要版本的Scala很可能不会有这个问题。 - Daniel C. Sobral
在Scala中,以:结尾的运算符是从右向左读取的。你开玩笑吧?我觉得Scala设计者对于特定运算符硬编码规则的恐惧与不得不处理令人困惑的各种运算符规则的现实并不一致。 - Rag
显示剩余2条评论

3
这是关于优先级而不是执行顺序的问题。+ 的优先级高于::,因此a + b :: c将解析为(a + b) :: c。然而,具有常规名称的中缀方法调用的优先级较低,因此a foo b c将解析为a foo (b c)
请参见此问题,以获取按其在Scala中的优先级排序的运算符列表。

1
谢谢您的解释。优先级是我刚刚忘记考虑的。 - ciuncan

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