Почему классы значений ограничены AnyVal?


Насколько я понимаю, классы значений в Scala существуют только для того, чтобы переносить примитивные типы, такие как Int или Boolean, в другой тип без дополнительного использования памяти. Поэтому они в основном используются как легкая альтернатива обычным классам.

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

Мне интересно, почему концепция введения новых типов, которые вводятся компилятором, не обобщается на подход Хаскелла, заключающийся в том, чтобы иметь обертки типов с нулевыми накладными расходами для любого типа. Почему ребята из Scala придерживались здесь примитивных типов (он же AnyVal)?

Или в Scala уже есть способ также определить такие оболочки для типов Scala.AnyRef?

2 3

2 ответа:

Они не ограничиваются AnyVal.

implicit class RichOptionPair[A,B](val o: Option[(A,B)]) extends AnyVal {
  def ofold[C](f: (A,B) => C) = o map { case (a,b) => f(a,b) }
}

scala> Some("fish",5).ofold(_ * _)
res0: Option[String] = Some(fishfishfishfishfish)
Существуют различные ограничения на классы значений, которые заставляют их действовать как легкие оболочки, но только возможность обернуть примитивы не является одним из них.

Аргументация документируется какпроцесс улучшения Scala (SIP)-15 . Как отметил в своем комментарии Алексей Романов, идея состояла в том, чтобы искать выражение, используя существующие ключевые слова, которые позволили бы компилятору определить эту ситуацию.

Для выполнения компилятором инлайнинга применяется несколько ограничений, например, класс обертывания является "эфемерным" (нет поля или объекта-члена, тела конструктора и т. д.). Ваше предложение о автоматической генерации инлайнинга классы имеют по крайней мере две проблемы:

    Компилятор должен будет пройти через весь список ограничений для каждого класса. А поскольку класс status as value неявен, он может перевернуться, добавив члены в класс в более поздний момент, нарушая двоичную совместимость
  1. дополнительные ограничения добавляются компилятором, например, класс значение становится final, запрещающие наследование. Таким образом, вы должны были бы добавить эти ограничения к любому классу, который хочет быть inlineable таким образом, а затем вы не получаете ничего, кроме дополнительного многословия.

Можно было бы придумать и другие гипотетические конструкции, например val class Meter(underlying: Double) { ... }, но преимущество extends AnyVal IMO заключается в том, что не требуется никаких синтаксических расширений. Кроме того, все примитивные типы расширяются AnyVal, поэтому есть хорошая аналогия (нет ссылки, нет наследования, эффективного представления и т. д.)