简体   繁体   中英

Why do I get a List when I create a Seq

Why do I get a List when I create Seq using the following code?

    scala> val s = Seq[Int]()
    s: Seq[Int] = List()

In fact, Seq is a trait and trait cannot be initialized. What't the magic behind the scene

Seq is a trait, List is a (default) implementation.

object Seq extends SeqFactory.Delegate[Seq](List)
//                                          ^^^^

https://github.com/scala/scala/blob/v2.13.10/src/library/scala/collection/immutable/Seq.scala#L39

implicitly[List[Int] <:< Seq[Int]] // compiles, i.e. List[Int] is a subtype of Seq[Int]

Seq is a trait and trait cannot be initialized

Firstly, even if X is a trait you can instantiate an anonymous class extending the trait: new X {} . (By the way, List is also an abstract class.)

Secondly, Seq[Int]() is desugared into Seq.apply[Int]() and you refer to not the trait Seq but its companion object.

You called apply from the Seq companion object. The implementation of apply returned a List[Int] , which is an instance of Seq[Int]

There is no magic.

There is simply some code which looks roughly like this:

object Seq:
  def apply[A](elements: A*) = List(elements: _*)

In other words, Seq.apply delegates to List.apply , so when you call Seq(1, 2, 3) , it delegates to List(1, 2, 3) , which returns a List .

Here is an extremely simplified runnable Scastie demonstration :

enum MyList[+A]:
  case MyNil
  case MyCons(head: A, tail: MyList[A])

object MySeq:
  def apply[A](elements: A*) =
    elements.foldRight(MyList.MyNil: MyList[A]) { (x, xs) =>
      MyList.MyCons(x, xs)
    }

MySeq()        //=> MyNil: MyList[Nothing]

MySeq(1, 2, 3) //=> MyCons(1, MyCons(2, MyCons(3, MyNil))): MyList[Int]

The real code is of course more general and abstract than this simple example. The Seq object does not have an apply method of its own . Instead, it inherits from SeqFactory.Delegate[Seq](List) :

object Seq extends SeqFactory.Delegate[Seq](List) {
//         ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
  override def from[E](it: IterableOnce[E]): Seq[E] = ???
}

SeqFactory.Delegate[Seq](List) 's apply method , in turn, delegates to the apply method of the object which was passed as the delegate argument:

object SeqFactory {
  class Delegate[CC[A]](delegate: SeqFactory[CC]) extends SeqFactory[CC] {
  //                    ↑↑↑↑↑↑↑↑
    override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*)
  //                                          ↑↑↑↑↑↑↑↑
  }

The delegate argument , if you remember, was the List object :

object Seq extends SeqFactory.Delegate[Seq](List) {
//                                          ↑↑↑↑
  override def from[E](it: IterableOnce[E]): Seq[E] = ???
}

List doesn't have its own apply method , instead it inherits it from IterableFactory , which looks like this :

def apply[A](elems: A*): CC[A] = from(elems)

So, it just delegates to from , which is an abstract method which it implements like this :

def from[B](coll: collection.IterableOnce[B]): List[B] = Nil.prependedAll(coll)

So, from delegates to Nil.prependedAll . Nil inherits prependedAll from List , where it looks like this :

override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): List[B] = prefix match {
  case xs: List[B] => xs ::: this
  case _ if prefix.knownSize == 0 => this
  case b: ListBuffer[B] if this.isEmpty => b.toList
  case _ =>
    val iter = prefix.iterator
    if (iter.hasNext) {
      val result = new ::[B](iter.next(), this)
      var curr = result
      while (iter.hasNext) {
        val temp = new ::[B](iter.next(), this)
        curr.next = temp
        curr = temp
      }
      releaseFence()
      result
    } else {
      this
    }
}

In your case, where you are constructing an empty Seq , you will hit the second case :

case _ if prefix.knownSize == 0 => this

Which just returns this , which is Nil , which is an instance of List[A] . Therefore, the runtime class of Seq[Int]() is List[Int] , but of course, the static type is still Seq[Int] .

The most general case is the fourth case, which gets the iterator of the prefix and then loops over the iterator in a while loop. Note: this is not "the Scala way", The Scala standard library takes some shortcuts in the interest of performance and/or platform interoperability and/or simply caused by the fact that at this particular point in the library, the high-level abstractions we are used to are not available. because it is the standard library which provides those abstractions in the first place.

The Scala way to do this would be a fold , like I did above.

So, even though the real Scala library goes through half a dozen additional layers of indirection, it is pretty much equivalent to the simplified code snippet I wrote above.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM