Параметры наследования и типа Traversable - PullRequest
8 голосов
/ 29 апреля 2010

Я изучаю исходный код классов коллекции Scala 2.8. У меня есть вопросы по поводу иерархии scala.collection.Traversable. Посмотрите на следующие объявления:

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]

Вопрос: почему Traversable расширяется GenericTraversableTemplate с параметрами типа [A, Traversable] - почему бы не [A, Traversable[A]]? Я попытался поэкспериментировать с небольшой программой с такой же структурой и получил странное сообщение об ошибке, когда попытался изменить его на Traversable[A]:

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

Я думаю, что использование аннотации @uncheckedVariance в GenericTraversableTemplate также имеет отношение к этому? (Это похоже на потенциально небезопасный взлом, чтобы заставить вещи работать ...).

edit - нашел несколько полезных ответов об аннотации в этот вопрос (потому что GenericTraversableTemplate используется как для изменяемых, так и для неизменяемых коллекций, которые имеют разную дисперсию). *

Вопрос: Когда вы смотрите на иерархию, вы видите, что Traversable наследует HasNewBuilder дважды (один раз через TraversableLike и один раз через GenericTraversableTemplate), но с немного другими параметрами типа. Как это работает точно? Почему параметры разных типов не вызывают ошибку?

1 Ответ

16 голосов
/ 29 апреля 2010

Причина - параметр CC в признаке GenericTraversableTemplate. В отличие от обычного параметра типа, который имеет вид * (произносится как «тип»), этот параметр имеет тип * => * (произносится как «тип-тип»). Чтобы понять, что это значит, сначала нужно немного рассказать о видах.

Рассмотрим следующий фрагмент:

val a: Int = 42

Здесь мы видим 42, что является значением . Значения имеют внутренние типы. В этом случае наше значение равно 42, а тип - Int. Тип - это что-то вроде категории, которая включает в себя множество значений. Это говорит кое-что о значениях, которые возможны для переменной a. Например, мы знаем, что a не может содержать значение "foobar", потому что это значение имеет тип String. Таким образом, значения подобны первому уровню абстракции, а типы на один уровень выше значений.

Итак, вот вопрос: что мешает нам сделать этот шаг вперед? Если значения могут иметь типы, почему типы не могут иметь «что-то» над ними? Это «что-то» называется kind . Виды относятся к типам, а типы - к значениям, могут быть описаны общие категории, которые ограничивают тип типов .

Давайте рассмотрим несколько конкретных примеров:

type String
type Int
type List[Int]

Это типы, и все они имеют вид *. Это самый распространенный вид (именно поэтому мы называем его «тип»). На практике большинство типов имеют такой вид. Однако некоторые этого не делают:

type List     // note: compile error

Здесь у нас есть конструктор типа List, но на этот раз мы «забыли» указать его параметр типа. Оказывается, это на самом деле тип, но другого типа. В частности, * => *. Поскольку обозначение подразумевает, что этот тип описывает тип, который принимает в качестве параметра другой тип *, в результате получается новый тип *. Мы можем видеть это в первом примере, где мы передали тип Int (который имеет вид *) конструктору типа List (который имеет вид * => *), создавая тип List[Int] (который имеет вид *).

Возвращаясь к GenericTraversableTemplate, давайте снова посмотрим на объявление:

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

Обратите внимание, как параметр типа CC принимает собственный параметр, но этот параметр не определен никаким другим параметром типа в объявлении? Это довольно неуклюжий способ сказать, что CC должен иметь вид * => * (точно так же, как a должен иметь тип Int в нашем предыдущем примере). Параметры «нормального» типа (например, A) всегда имеют вид *. Приводя CC к виду * => *, мы фактически сообщаем компилятору, что единственными допустимыми типами, которые могут быть заменены для этого параметра, должны быть сами по себе * => *. Таким образом:

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

Помните, List имеет вид * => * (именно то, что нам нужно для CC), но List[Int] имеет вид *, поэтому компилятор отклоняет его.

Для записи, GenericTraversableTemplate сам по себе имеет вид, а именно: (* x (* => *)) => *. Это означает, что GenericTraversableTemplate является типом, который принимает два типа в качестве параметров - один типа *, другой типа * => * - и в результате выдает тип типа *. В нашем примере выше GenericTraversableTemplate[String, List] является одним из таких типов результатов, и, как мы вычислили, он имеет вид * (он не принимает параметров).

...