Почему мы должны явно указывать класс типов ClassTag

Теперь, когда scala перешла к исправлению стирания типа JVM с классом типов ClassTag, почему это необязательно, а не то, что компилятор всегда собирает сигнатуру типа для проверки во время выполнения. Наличие неявного параметризованного ограничения типа позволило бы вызывать classTag[T] независимо от объявления универсального параметра.

EDIT: я должен уточнить, что я не имею в виду, что scala должна изменять подпись за кулисами, чтобы всегда включать ClassTag. Скорее я имею в виду, что, поскольку ClassTag показывает, что scala может собирать информацию о типах во время выполнения и, следовательно, избегать ограничений на стирание типов, почему этот захват не может быть неявным как часть компилятора, чтобы эта информация всегда была доступна в коде scala?

Я подозреваю, что это обратная совместимость, совместимость с экосистемой Java, размер двоичного файла или накладные расходы во время выполнения, но это всего лишь предположения.


person Arne Claassen    schedule 17.05.2015    source источник


Ответы (2)


На самом деле обратная совместимость будет полностью уничтожена. Если у вас есть простой метод, например:

def foo[A](a: A)(implicit something: SomeType) = ???

Затем предположим, что в следующей версии Scala компилятор вдруг добавил неявные ClassTag в сигнатуру всех методов с параметрами типа. Этот метод будет нарушен. Везде, где он явно вызывался, как foo(a)(someTypeValue), больше не будет работать. Двоичная и исходная совместимость исчезнет.

Совместимость с Java была бы уродливой. Предположим, что наш метод теперь выглядит так:

def foo[A : ClassTag](a: A) = ???

Поскольку ClassTag создаются компилятором Scala, использовать этот метод из Java будет сложнее. Вам придется создать ClassTag самостоятельно.

ClassTag<MyClass> tag = scala.reflect.ClassTag$.MODULE$.apply(MyClass.class);
foo(a, tag);

Моя Java может быть не на 100% правильной, но вы поняли. Все, что параметризовано, станет очень уродливым. Ну, это уже так, если для этого требуется неявный ClassTag, но класс методов, где это было бы необходимо, резко увеличился бы.

Кроме того, стирание типов не является большой проблемой в большинстве параметризованных методов, которые мы (по крайней мере, я) используем. Я думаю, что автоматическое требование ClassTag для каждого параметра типа было бы гораздо более проблематичным, чем помогло бы, по вышеуказанным причинам.

Конечно, это добавит дополнительных накладных расходов компилятору, так как потребуется генерировать больше ClassTags, чем обычно. Я не думаю, что это добавит гораздо больше накладных расходов во время выполнения, если только ClassTag не изменит ситуацию. Например, в простом методе, как показано ниже, ClassTag ничего не делает:

def foo[A : ClassTag](a: A): A = a

Мы также должны отметить, что они тоже не идеальны. Таким образом, их добавление не является окончательным решением проблем стирания.

val list = List(1, "abc", List(1, 2, 3), List("a", "b"))
def find[A: ClassTag](l: List[Any]): Option[A] =
    l collectFirst { case a: A => a }

scala> find[List[String]]
res2: Option[List[String]] = Some(List(1, 2, 3)) // Not quite! And no warnings, either.

Добавление ClassTag к каждому отдельному экземпляру класса добавит накладные расходы и, конечно же, нарушит совместимость. Это также во многих местах невозможно. Мы не можем просто смешать java.lang.String с ClassTag. Кроме того, мы по-прежнему были бы так же восприимчивы к стиранию. Наличие поля ClassTag в каждом классе на самом деле не лучше, чем использование getClass. Мы могли проводить сравнения, например

case a if(a.getClass == classOf[String]) => a.asInstanceOf[String]

Но это ужасно уродливо, требует гипса и не обязательно предназначено для исправления ClassTag. Если бы я попробовал что-то подобное с моим методом find, это бы не сработало — вообще.

// Can't compile
def find[A](l: List[Any]): Option[A] =
    l collectFirst { case a if(a.getClass == classOf[A]) => a.asInstanceOf[A] }

Даже если бы я приспособил это для работы с ClassTag, откуда бы это взялось? Я не мог сказать a.classTag == classTag[A], потому что A уже стерто. Мне нужно ClassTag на сайте вызова метода.

person Michael Zajac    schedule 17.05.2015
comment
Я больше думал о неявном хранении ClassTag в качестве метаданных класса, доступных для кода scala, а не о фактическом переписывании подписи для использования ClassTag. Я полагаю, что это просто мой опыт отражения в .NET делает меня более чувствительным к стиранию типов. - person Arne Claassen; 18.05.2015
comment
Однако, если вы сохраните ClassTag в реальных классах, то нам не намного лучше, чем просто использовать getClass и classOf[A]. Параметры типа метода было бы труднее восстановить, потому что мы полагались бы на переданные экземпляры вместо самого параметра типа. Мой метод find никогда бы не работал без неявного ClassTag, потому что A стирается. - person Michael Zajac; 18.05.2015
comment
Ах, верно. Я наделил ClassTag большими возможностями, чем он есть. Он не столько инструментирует типы в скомпилированном коде, сколько передает свидетельство от вызывающего экземпляра, что означает, что это не волшебное стирание лекарство. Спасибо за подробную информацию - person Arne Claassen; 19.05.2015

Да, вы бы оштрафовали любой общий метод или класс за случай использования, который встречается довольно редко (например, требующий построения массива или восстановления разнородного значения карты). С этой идеей «всегда использовать теги класса» вы также эффективно уничтожите возможность вызова кода Scala из Java. Летом просто не имеет смысла требовать, чтобы тег класса всегда присутствовал, ни с точки зрения совместимости, ни с точки зрения производительности, ни с точки зрения размера класса.

person 0__    schedule 18.05.2015