3

What exactly happens when you evaluate the expression: Seq(1,2,3)?

I am new to Scala and I am now a bit confused about the various collection types. Seq is a trait, right? So when you call it like this: Seq(1,2,3), it must be some kind of a companion object? Or not? Is it some kind of a class that extends Seq? And most importantly, what is the type of the returned value? Is it Seq and if yes, why is it not explicitly the extension class instead?

Also in the REPL I see that the contents of the evaluated expression is actually a List(1,2,3), but the type is apparently Seq[Int]. Why is it not an IndexedSeq collection type, like Vector? What is the logic behind all that?

Alin Gabriel Arhip
  • 2,568
  • 1
  • 14
  • 24
hellmean
  • 121
  • 10
  • 1
    Yes you call an [apply method](https://www.scala-lang.org/api/2.12.3/scala/collection/Seq$.html#apply[A](elems:A*):CC[A]) of the companion object (btw the documentation links between companion object and class on the top right). The `A*` type allows for an arbitrary number of arguments that are treated as a sequence of As. In your particular case, that sequence is created by the parser. Formally it would be passed to `apply` to create a copy but I would assume that the compiler optimizes this case. – lambda.xy.x May 15 '19 at 11:02
  • 1
    I'd say `List[T]` is the standard implementation of `Seq[T]` because of its simplicity -- for example vectors may need to be resized but a lot of functional code extends a list just with a new head. For indexed access, there is [IndexedSeq](https://www.scala-lang.org/api/2.12.3/scala/collection/IndexedSeq.html) which has a Vector as default implementation. – lambda.xy.x May 15 '19 at 11:04
  • 1
    Yes, `Seq` is a **trait**, and yes, you are calling the `apply` _method_ on its **companion object**. The return type is `Seq[A]`, where A is the type of the elements you passed to the factory _(in your case `Int`)_. The logic for returning a `List`, is because that is the standard collection in **Scala** given it is perfect for recursive algorithms and for higher order functions like `map`, `flatMap`, `filter` & `foldLeft`, which are pretty common in _idiomatic_ Scala code. And why the return type is `Seq` and not `List`, because it may change and that would break a lot of code. – Luis Miguel Mejía Suárez May 15 '19 at 12:13

2 Answers2

8

What exactly happens when you evaluate expression: Seq(1,2,3)?

In Scala, foo(bar) is syntactic sugar for foo.apply(bar), unless this also has a method named foo, in which case it is a method call on the implicit this receiver, i.e. just like Java, it is then equivalent to this.foo(bar).

Just like any other OO language, the receiver of a method call alone decides what to do with that call, so in this case, Seq decides what to do.

Seq is a trait, right?

There are two Seqs in the standard library:

So when you call it like that Seq(1,2,3) it must be some kind of a companion object? Or not?

Yes, it must be an object, since you can only call methods on objects. You cannot call methods on types, therefore, when you see a method call, it must be an object. Always. So, in this case, Seq cannot possibly be the Seq trait, it must be the Seq object.

Note that "it must be some kind of a companion object" is not true. The only thing you can see from that piece of code is that Seq is an object. You cannot know from that piece of code whether it is a companion object. For that, you would have to look at the source code. In this particular case, it turns out that it is, in fact, a companion object, but you cannot conclude that from the code you showed.

Is it some kind of a class that extends Seq?

No. It cannot possibly be a class, since you can only call methods on objects, and classes are not objects in Scala. (This is not like Ruby or Smalltalk, where classes are also objects and instances of the Class class.) It must be an object.

And most importantly what is the type of the returned value?

The easiest way to find that out is to simply look at the documentation for Seq.apply:

def apply[A](elems: A*): Seq[A]

Creates a collection with the specified elements.

  • A: the type of the collection's elements
  • elems: the elements of the created collection
  • returns a new collection with elements elems

So, as you can see, the return type of Seq.apply is Seq, or more precisely, Seq[A], where A is a type variable denoting the type of the elements of the collection.

Is it Seq and if yes, why is not explicitly the extension class instead?

Because there is no extension class.

Also, the standard design pattern in Scala is that the apply method of a companion object returns an instance of the companion class or trait. It would be weird and surprising to break this convention.

Also in REPL I see that the contents of the evaluated expression is actually a List(1,2,3), but the type is apparently Seq[Int].

The static type is Seq[Int]. That is all you need to know. That is all you can know.

Now, Seq is a trait, and traits cannot be instantiated, so the runtime type will be some subclass of Seq. But! You cannot and must not care, what specific runtime type it is.

Why is not an Indexed collection type, like Vector? What is the logic behind all that?

How do you know it is not going to return a Vector the next time you call it? It wouldn't matter one bit, since the static type is Seq and thus you are only allowed to call Seq methods on it, and you are only allowed to rely on the contract of Seq, i.e. Seq's post-conditions, invariants, etc. anyway. Even if you knew it was a Vector that is returned, you wouldn't be able to do anything with this knowledge.

Thus, Seq.apply returns the simplest thing it can possibly return, and that is a List.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
4

Seq is the val of:

package object scala {
...
  val Seq = scala.collection.Seq
...
}

it points to object scala.collection.Seq:

/** $factoryInfo
 *  The current default implementation of a $Coll is a `List`.
 *  @define coll sequence
 *  @define Coll `Seq`
 */
object Seq extends SeqFactory[Seq] {
  /** $genericCanBuildFromInfo */
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Seq[A]] = ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]

  def newBuilder[A]: Builder[A, Seq[A]] = immutable.Seq.newBuilder[A]
}

and when you do Seq(1,2,3) the apply() method is ivoked from scala.collection.generic.GenericCompanion abstract class:

/** A template class for companion objects of "regular" collection classes
 *  represent an unconstrained higher-kinded type. Typically
 *  such classes inherit from trait `GenericTraversableTemplate`.
 *  @tparam  CC   The type constructor representing the collection class.
 *  @see [[scala.collection.generic.GenericTraversableTemplate]]
 *  @author Martin Odersky
 *  @since 2.8
 *  @define coll  collection
 *  @define Coll  `CC`
 */
abstract class GenericCompanion[+CC[X] <: GenTraversable[X]] {
...
  /** Creates a $coll with the specified elements.
   *  @tparam A      the type of the ${coll}'s elements
   *  @param elems  the elements of the created $coll
   *  @return a new $coll with elements `elems`
   */
  def apply[A](elems: A*): CC[A] = {
    if (elems.isEmpty) empty[A]
    else {
      val b = newBuilder[A]
      b ++= elems
      b.result()
    }
  }
}

and finally, this method builds an object of Seq type by code mentioned above

And most importantly what is the type of the returned value?

object MainClass {

  def main(args: Array[String]): Unit = {

    val isList = Seq(1,2,3).isInstanceOf[List[Int]]

    println(isList)
  }
}

prints:

true

So, the type is scala.collection.immutable.List

Also in REPL I see that the contents of the evaluated expression is actually a List(1,2,3), but the type is apparently Seq[Int].

The default implementation of Seq is List by the code mentioned above.

Why is not an Indexed collection type, like Vector? What is the logic behind all that?

Because of immutable design. The list is immutable and to make it immutable and have a constant prepend operation but O(n) append operation cost and O(n) cost of accessing n'th element. The Vector has a constant efficient implementation of access and add elements by id, prepend and append operations.

To have a better understanding of how the List is designed in Scala, see https://mauricio.github.io/2013/11/25/learning-scala-by-building-scala-lists.html

Matthew I.
  • 1,793
  • 2
  • 10
  • 21
  • 1
    The type is `Seq`. In some particular version of some particular implementation at some particular point in time under some particular set of circumstances, the returned value may be a runtime instance of `List`, but the only guarantee that is made is that its static type is `Seq`. (In particular, there is no guarantee made at all about the runtime type of the returned value, so it would be completely legal to return, for example, an `ArrayBuffer` instance instead. You cannot rely on the runtime type being a `List`, the only thing you can rely on is the static type being `Seq`.) – Jörg W Mittag May 15 '19 at 12:17
  • @JörgWMittag yes, you are right bc Seq has a lot of subclasses, but at this particular situation, we got the List. I've mentioned that List is default implementation of Seq. – Matthew I. May 15 '19 at 12:21
  • the whole thing is very confusing because the following expression returns an error: `1 :: Seq(2,3)`. – hellmean May 15 '19 at 12:37
  • @hellmean ```1 :: Seq(2,3).asInstanceOf[List[Int]]``` works well – Matthew I. May 15 '19 at 12:41
  • @hellmean: Yes, because as I explained in my comment above, the type of `Seq(2, 3)` is `Seq` and not `List`, and `Seq` does not have a method named `::`. The return value may be an runtime instance of the `List` class, but that is irrelevant here. What is relevant is the type, and that is `Seq`. – Jörg W Mittag May 15 '19 at 12:41