为什么我可以在模式匹配中使用:: operator和Seq,但在其他地方不能

时间:2015-06-20 22:45:52

标签: scala

所以我对Scala中Seq的这种行为感到很困惑。

使用模式匹配时,我可以使用::+:运算符,它们似乎可以互换

val s=Seq(1,2,3)
s match{
case x :: l => ...

但是当我尝试在不同情况下使用::时:

val s=1::Seq(2,3)

我收到"value :: is not a member of Seq[Int]"条消息。我知道我应该使用+==+运算符与Seq,但为什么 ::仅适用于模式匹配方案吗?

2 个答案:

答案 0 :(得分:8)

::适用于List,实际上Seq.apply目前会为您提供List

scala> val s = Seq(1,2,3)
s: Seq[Int] = List(1, 2, 3)

因此值s的类型为Seq[Int],但它指向的对象的类型为List[Int]。这很好,因为List扩展了Seq。这当然会与涉及::的模式匹配,因为它实际上是List

scala> s match { case x :: xs => x }
res2: Int = 1

但表达式Seq(1,2,3)的类型不是List[Int]而是Seq[Int] - 即使实际对象确实是List。因此,以下操作失败,因为Seq未定义::方法:

scala> val s = 1 :: Seq(2,3)
<console>:7: error: value :: is not a member of Seq[Int]
       val s = 1 :: Seq(2,3)

您必须使用Seq的方法:

scala> val s = 1 +: Seq(2,3)
s: Seq[Int] = List(1, 2, 3)

您混淆的关键是当您对s之类的值调用方法时,可用的方法集完全取决于值 static 类型,而模式匹配检查匹配的对象是否为::类。

为了显示这一点,让我们编译一些示例代码并使用javap来查看字节码; first方法的前几条指令检查参数是否为类::(而不是扩展Seq的其他类)并强制转换为它:

NS% cat Test.scala
object Test {
  def first(xs: Seq[Int]) = xs match { case x :: xs => x }
}

NS% javap -c Test\$.class
Compiled from "Test.scala"
public final class Test$ {
  public static final Test$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class Test$
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public int first(scala.collection.Seq<java.lang.Object>);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: instanceof    #16                 // class scala/collection/immutable/$colon$colon
       6: ifeq          30
       9: aload_2
      10: checkcast     #16                 // class scala/collection/immutable/$colon$colon
      13: astore_3
      14: aload_3
      15: invokevirtual #20                 // Method scala/collection/immutable/$colon$colon.head:()Ljava/lang/Object;
      18: invokestatic  #26                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
      21: istore        4
      23: iload         4
      25: istore        5
      27: iload         5
      29: ireturn
      30: new           #28                 // class scala/MatchError
      33: dup
      34: aload_2
      35: invokespecial #31                 // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
      38: athrow

最后,您可以问为什么Scala人没有为::制作Seq等效方法(前置元素)。如果有,那么1 :: Seq(2,3)就可以了。

但对于Seq,他们确实需要一个的运算符,一个要预先添加(这个必须以冒号结束,以便它是右关联的)和一个要追加的运算符。您希望避免将元素附加到List,因为您必须遍历现有元素才能执行此操作,但对于Seq一般情况并非如此 - 例如追加对Vector非常有效。因此,他们选择+:作为前置,:+作为追加。

当然,您可以问为什么他们没有使用+: List来匹配Seq。我不知道答案的全部答案。我知道::来自其他具有列表结构的语言,因此部分答案可能与已建立的约定一致。也许他们没有意识到他们想要一对匹配的{@ 1}}超类型算子,直到为时已晚 - 不确定。有谁知道这里的历史?

答案 1 :(得分:2)

::发音consLists的运算符。 Seq是所有Scala序列继承的通用特征。这是任何类型序列的通用接口,而不仅仅是列表。

鉴于默认情况下Scala使用Lists作为Seq()工厂方法返回的序列,因此模式匹配可以使用缺点。

所以你可以做到

val s = 1::List(2,3)

但不是

val s = 1::Seq(2,3)