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.