Возможно ли для Scala овеществление дженериков без изменения JVM? - PullRequest
28 голосов
/ 31 августа 2009

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

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

Ответы [ 5 ]

21 голосов
/ 31 августа 2009

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

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

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

5 голосов
/ 31 августа 2009

Просто в дополнение к oxbow_lakes, есть вопрос о переполнении стека о как обойти стирание типов в Scala .

3 голосов
/ 28 июля 2013

В дополнение к ответу oxbow_lakes: Это невозможно, и кажется, что этого никогда не произойдет (по крайней мере, в ближайшее время).

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

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

Ссылки:

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

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

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

3 голосов
/ 01 сентября 2009

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

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]
1 голос
/ 01 февраля 2011

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

Я имею в виду, что скаляк будет способен видеть ...

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

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

... и "развернуть" что-то вроде этого:

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

// calls
floats  = Klass_float
doubles = Klass_double

Редактировать

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

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

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

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

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

...