Inheritance and type parameters of Traversable
Asked Answered
L

1

8

I'm studying the source code of the Scala 2.8 collection classes. I have questions about the hierarchy of scala.collection.Traversable. Look at the following declarations:

package scala.collection
    trait Traversable[+A]
        extends TraversableLike[A, Traversable[A]] 
        with GenericTraversableTemplate[A, Traversable]

    trait TraversableLike[+A, +Repr]
        extends HasNewBuilder[A, Repr]
        with TraversableOnce[A]

package scala.collection.generic
    trait HasNewBuilder[+A, +Repr]

    trait GenericTraversableTemplate[+A, +CC[X] <: Traversable[X]]
        extends HasNewBuilder[A, CC[A] @uncheckedVariance]

Question: Why does Traversable extend GenericTraversableTemplate with type parameters [A, Traversable] - why not [A, Traversable[A]]? I tried some experimenting with a small program with the same structure and got a strange error message when I tried to change it to Traversable[A]:

error: Traversable[A] takes no type parameters, expected: one

I guess that the use of the @uncheckedVariance annotation in GenericTraversableTemplate also has to do with this? (That seems like a kind of potentially unsafe hack to force things to work...).

edit - found some useful answers about the annotation in this question (it is because GenericTraversableTemplate is used for both mutable and immutable collections which have different variance).

Question: When you look at the hierarchy, you see that Traversable inherits HasNewBuilder twice (once via TraversableLike and once via GenericTraversableTemplate), but with slightly different type parameters. How does this work exactly? Why don't the different type parameters cause an error?

Leathern answered 28/4, 2010 at 23:22 Comment(0)
B
17

The reason is the CC parameter in the GenericTraversableTemplate trait. Unlike a normal type parameter which has kind * (pronounced "type"), this parameter has type * => * (pronounced "type to type"). In order to understand what this means, you first need to have a little background about kinds.

Consider the following snippet:

val a: Int = 42

Here we see 42, which is a value. Values have intrinsic types. In this case, our value is 42 and the type is Int. A type is something like a category which encompasses many values. It says something about the values which are possible for variable a. For example, we know that a cannot contain the value "foobar", because that value has type String. Thus, values are sort of like the first level of abstraction, while types are one level above values.

So here's the question: what's stopping us from taking this one step further? If values can have types, why can't types have "something" above them? That "something" is called a kind. Kinds are to types what types are to values, generic categories which restrict what sort of types can be described.

Let's look at some concrete examples:

type String
type Int
type List[Int]

These are types, and they all have kind *. This is the most common kind (which is why we call it "type"). In practice, most types have this kind. However, some do not:

type List     // note: compile error

Here we have the type constructor List, but this time we "forgot" to specify its type parameter. As it turns out, this is actually a type, but one of a different kind. Specifically, * => *. As the notation is meant to imply, this kind describes a type which takes another type of kind * as a parameter, producing a new type of kind * as a result. We can see this in the first example, where we passed the Int type (which has kind *) to the List type constructor (which has kind * => *), producing the type List[Int] (which has kind *).

Returning to GenericTraversableTemplate, let's look again at the declaration:

trait GenericTraversableTemplate[+A, +CC[X] <: Traversable[X]]

Notice how the CC type parameter takes a parameter of its own, but that parameter is not defined by any other type parameter in the declaration? This is Scala's rather clumsy way of saying that CC must be of kind * => * (just like a must be of type Int in our earlier example). "Normal" type parameters (such as A) are always of kind *. By forcing CC to be of kind * => *, we're effectively telling the compiler that the only valid types which can be substituted for this parameter must be themselves of kind * => *. Thus:

type GenericTraversableTemplate[String, List]        // valid!
type GenericTraversableTemplate[String, List[Int]]   // invalid!

Remember, List is of kind * => * (exactly what we need for CC), but List[Int] has kind *, so the compiler rejects it.

For the record, GenericTraversableTemplate itself has a kind, specifically: (* x (* => *)) => *. This means that GenericTraversableTemplate is a type which takes two types as parameters -- one of kind *, the other of kind * => * -- and produces a type of kind * as a result. In our example above, GenericTraversableTemplate[String, List] is one such result type, and as we computed, it is of kind * (it takes no parameters).

Brigitta answered 29/4, 2010 at 4:57 Comment(1)
Thanks for taking the time to answer this so clearly! I'd heard about "kinds" before but didn't really understand what they mean until now.Leathern

© 2022 - 2024 — McMap. All rights reserved.