logo
Язык Scala

Моделирование обобщенных (generic) типов с помощью абстрактных типов

Наличие двух абстракций типов в одном языке поднимает вопрос о сложности языка - нельзя ли было обойтись одной? В этом разделе мы покажем, что функциональная абстракция типов (ака обобщенные типы) может в принципе быть смоделирована с помощью объектно-ориентированной абстракции типов (иначе абстрактных типов). Одну форму можно трансформировать в другую. Идея трансформации в общих чертах описана ниже.

Предположим, что у нас есть параметризованный класс C с параметром типа Т (код напрямую обобщается в множественные параметры типов). Трансформация состоит из четырех частей, затрагивающих определение класса, создание экземпляров класса, вызовы конструкторов базового класса и экземпляры типов класса.

1. Определение класса C трансформируется так:class C

{

type t;

/* остальная часть класса */

}

Таким образом, параметры исходного класса моделируются с помощью абстрактных членов трансформированного класса. Если параметр типа t имеет сужения и/или расширения, они переносятся в определение абстрактного типа. Вариантность параметров типа не переносится; вместо этого вариантность влияет на формирование типов (см. пункт 4).

2. Создание каждого экземпляра new C[T] с аргументом типа Т трансформируется в:new C { type t = T }

3. Если C[T] выступает в роли конструктора суперкласса, его классы-наследники дополняются определением:type t = T

4. Каждый из типов C[T] трансформируется в один из следующих типов, каждый из которых дополняет класс С уточнением:

C { type t = T } если t объявлен не вариантным,

C { type t <: T } если t объявлен ковариантным,

C { type t >: T } если t объявлен контрвариантным.

Такой код работает, если не встречается конфликтов имен. Поскольку имя параметра становится членом класса, оно может конфликтовать с другими членами, включая унаследованные члены, сгенерированные по именам параметров базовых классов. Этих конфликтов имен можно избежать переименованием, например, дополнением каждого имени уникальным номером.

Возможность трансформации из одного стиля абстракции в другой полезна, так как снижает концептуальную сложность языка. В случае Scala обобщенные типы становятся не более, чем "синтаксическим сахаром", который можно устранить трансформацией в абстрактные типы. Однако возникает вопрос, насколько обосновано наличие этого синтаксического сахара, и нельзя ли обойтись одними абстрактными типами, то есть ограничиться синтаксически меньшим языком. Есть два аргумента за включение обобщенных типов в Scala. Во-первых, трансформацию в абстрактные типы не так уж просто писать вручную. Это приводит потерь выразительности, и есть также проблема случайных конфликтов имен между именами абстрактных типов, эмулирующих параметры типов. Во-вторых, обобщенные и абстрактные типы обычно играют в Scala-программах различные роли. Обобщенные типы обычно используют, когда нужна только реализация экземпляра типа, а абстрактные типы - когда нужна ссылка на абстрактный тип из клиентского кода. Последнее встречается, в частности, в двух ситуациях. Может понадобиться спрятать точное определение члена типа от клиентского кода, чтобы получить нечто вроде инкапсуляции, известной по модульным системам в SML-стиле. Или же может потребоваться переопределить тип ковариантно в подклассах, чтобы получить семейный полиморфизм.

Можно ли пойти другим путем и перекодировать абстрактные типы в обобщенные? Оказывается, это значительно труднее, и требует полного переписывания программы. Это было показано в исследованиях в области модульных систем, где доступны оба вида абстракции [21]. На самом деле такая сложность неудивительна, если рассматривать проблему с точки зрения основ теории типов обеих систем. Обобщенные типы (без F-ограничений) могут выражаться в System F<: [описанной в 9], тогда как абстрактные типы требуют системы, основанной на зависимых типах. Последние, в общем, выразительнее предыдущих, например, ?Obj с его зависимыми от пути типами позволяет закодировать F<:.