Возможно ли, чтобы Scala обновил дженерики без изменения JVM?

Недавно я начал изучать Scala и был разочарован (но не удивлен), что их дженерики также реализованы с помощью стирания типа.

Мой вопрос: возможно ли, чтобы Scala обновил дженерики, или же JVM нужно каким-то образом изменить? Если JVM необходимо изменить, что именно нужно изменить?

Ответы

Ответ 1

Нет - невозможно, чтобы Scala выполнялся как байт-код, эквивалентный Java, если этот байт-код не поддерживает повторяющиеся дженерики.

Когда вы спрашиваете "что это нужно изменить?", ответ: спецификация байт-кода. В настоящее время байт-код не позволяет определить параметризованный тип переменной. Было решено, что в качестве модификации байт-кода для поддержки редифицированных генериков будет отменить обратную совместимость, что generics должны быть реализованы с помощью стирания типа.

Чтобы обойти это, Scala использовал силу своего механизма implicit для определения Manifest, который может быть импортирован в любую область для обнаружения информации о типе во время выполнения. Манифесты являются экспериментальными и в основном недокументированными, но они поступают как часть библиотеки в 2.8. Вот еще один хороший ресурс в Scala reified generics/Manifests

Ответ 3

"implicit Manifest" представляет собой трюк компилятора Scala, и он не генерирует дженериков в Scala reified. Компилятор Scala, когда он видит функцию с "неявным параметром m: Манифест [A]", а знает общий тип A на сайте вызова, он перенесет класс A и его параметры типового типа в манифест и сделать его доступным внутри функции. Однако, если он не мог определить истинный тип A, тогда у него нет способа создать манифест. Другими словами, манифест должен передаваться по цепочке вызова функции, если ей нужна внутренняя функция.

scala> def typeName[A](a: A)(implicit m: reflect.Manifest[A]) = m.toString
typeName: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String

scala> typeName(List(1))
res6: java.lang.String = scala.collection.immutable.List[int]

scala> def foo[A](a: A) = typeName(a)
<console>:5: error: could not find implicit value for parameter m:scala.reflect.Manifest[A].
       def foo[A](a: A) = typeName(a)
                                  ^

scala> def foo[A](a: A)(implicit m: reflect.Manifest[A]) = typeName(a)
foo: [A](a: A)(implicit m: scala.reflect.Manifest[A])java.lang.String

scala> foo(Set("hello"))
res8: java.lang.String = scala.collection.immutable.Set[java.lang.String]

Ответ 4

Чтобы дополнить oxbow_lakes ответ: это невозможно, и похоже, что это никогда не произойдет (по крайней мере, скоро).

(опровержимые) причины JVM не будет поддерживать повторяющиеся дженерики, кажется:

  • Более низкая производительность.
  • Он нарушает обратную совместимость. Это можно решить, дублируя и фиксируя множество библиотек.
  • Он может быть реализован с использованием манифестов: "Решение" и самое большое препятствие.

Литература:

Вы можете легко сравнить его и увидеть, что влияние производительности очень заметно. Особенно сильно увеличивается потребление памяти.

Я считаю, что путь - это необязательное овеществление, как мы начните делать в Scala с помощью Manifests/TypeTags.

Если вы можете и объединить его со специализацией во время выполнения, вы можете высокопроизводительный и общий код. Однако, возможно, для Scala 2.12 или 2.13.

Ответ 5

Как только scalac является компилятором, у него есть потенциал, позволяющий украсить сгенерированный код любыми структурами данных, необходимыми для реализации повторяющихся генериков.

Я имею в виду, что scalac будет иметь возможность видеть...

// definition
class Klass[T] {
  value : T
}

//calls
floats  = Klass[float]
doubles = Klass[double]

... и "expand" примерно так:

// definition
class Klass_float {
  value : float
}
class Klass_double {
  value : double
}

// calls
floats  = Klass_float
doubles = Klass_double

Edit

Дело в том, что компилятор имеет возможность создавать все необходимые структуры данных, которые демонстрируют необходимость предоставления дополнительной информации о типе во время выполнения. Как только информация этого типа будет доступна, среда выполнения Scala воспользуется ею и сможет выполнять все операции с типом, которые мы можем себе представить. Не имеет значения, поддерживает ли JVM байт-код для генерируемых дженериков или нет. Работа не выполняется JVM, а библиотекой Scala.

Если вы уже написали символический отладчик (я сделал!), вы знаете, что вы можете в основном "сбрасывать" всю информацию, которую компилятор имеет во время компиляции, в сгенерированный двоичный код, принимая любую организацию данных, которая демонстрирует, что она более удобна для дальнейшая обработка. Это точно та же идея: "дамп" вся информация о типе, которую имеет компилятор Scala.

Вкратце, я не понимаю, почему это невозможно, неважно, поддерживает ли JVM собственные операции для генерируемых дженериков или нет.

Другое редактирование

IBM X10 демонстрирует способность, о которой я говорю: он компилирует код X10 на Java-код, используя обобщенные генерики на платформах Java. Как я уже упоминал ранее: это можно сделать, но только люди, которые знают, как работает компилятор, и как библиотека времени выполнения может использовать информацию, "сбрасываемую" компилятором во время компиляции на сгенерированный код. Дополнительная информация: http://x10.sourceforge.net/documentation/papers/X10Workshop2012/slides/Takeuchi.pdf