Scala的'::'运算符是如何工作的?

61

在Scala中,我可以创建一个case class,case class Foo(x:Int),然后将其放入列表中:


List(Foo(42))

现在,这里没有什么奇怪的地方。接下来让我感到困惑的是,操作符::是列表上的一个函数,对吧?在Scala中,任何带有一个参数的函数都可以使用中缀表示法来调用。

例如1 + 2Int对象上的一个名为(+)的函数。我刚刚定义的Foo类没有::运算符,所以这是如何可能的呢?

Foo(40) :: List(Foo(2))
在Scala 2.8 RC1中,我从交互式提示符中得到以下输出:
scala> case class Foo(x:Int)
defined class Foo

scala> Foo(40) :: List(Foo(2))
res2: List[Foo] = List(Foo(40), Foo(2))

我可以继续使用它,但是它的解释是什么?

4个回答

57

根据规范:

6.12.3 中缀操作符一个中缀操作符可以是任意标识符。中缀操作符的优先级和结合性如下所示。

...

操作符的结合性由操作符的最后一个字符决定。以冒号‘:’结尾的操作符是右结合的。所有其他操作符都是左结合的。

您可以通过在编译器的'typer'阶段之后打印程序来随时查看这些规则在Scala中如何应用:

scala -Xprint:typer -e "1 :: Nil"

val r: List[Int] = {
  <synthetic> val x$1: Int = 1;
  immutable.this.Nil.::[Int](x$1)
};

1
它也适用于将类型参数传递给类型构造函数。假设您有一个案例类:: [H,T](head:H,tail:T); 和类SomeType [A]; 然后您可以执行new SomeType [:: [String,Int]](“a”,3)和new SomeType [H :: T](“a”,3)。 - lisak

24

该函数以 : 结尾。这表示该函数在右侧的类(这里是 List 类)中定义。

因此,在您的示例中应使用 List(Foo(2)).::(Foo(40)),而不是 Foo(40).::(List(Foo(2)))


4
换句话说,a ::::: b(只是为了好玩)与b.:::::(a)相同。此外,方法名称以冒号结尾且以中缀样式使用的方法将向右关联,而不是向左关联,因此a :: b :: c等同于c.::(b.::(a))) - Randall Schulz

21

在给出的答案中缺少一个方面,那就是支持在模式匹配表达式中使用::

List(1,2) match {
  case x :: xs => println(x + " " + xs)
  case _ => println("")
}

定义了一个名为::的类

final case class ::[B](private var hd: B, private[scala] var tl: List[B]) 

所以case ::(x,xs)会产生相同的结果。表达式case x :: xs之所以管用,是因为默认提取器::是为该样例类定义的,可以作为中缀使用。


20
我刚刚定义的类Foo没有::运算符,那么下面这个是怎么可能的呢:Foo(40) :: List(Foo(2))
如果方法名以冒号(:)结尾,则该方法在右操作数上调用,这就是此处的情况。如果方法名不以冒号结尾,则该方法在左操作数上调用。例如,对于表达式a + b+是在a上调用的。
因此,在您的示例中,::是其右操作数List上的一个方法。

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