Чтобы обеспечить полную гибкость в использовании видов, необходимо использовать систему видов для различения упакованных типов с подъёмом (обычных повседневных типов, таких как Int и [Bool]) и неупакованных примитивных типов (Неупакованные типы и примитивные операции), таких как Int#. Таким образом, мы получаем так называемый полиморфизм представления.
Ниже приведены ключевые определения, доступные из GHC.Exts:
TYPE :: RuntimeRep -> Type -- highly magical, built into GHC
data Levity = Lifted -- for things like `Int`
| Unlifted -- for things like `Array#`
data RuntimeRep = BoxedRep Levity -- for anything represented by a GC-managed pointer
| IntRep -- for `Int#`
| TupleRep [RuntimeRep] -- unboxed tuples, indexed by the representations of the elements
| SumRep [RuntimeRep] -- unboxed sums, indexed by the representations of the disjuncts
| ...
type LiftedRep = BoxedRep Lifted
type Type = TYPE LiftedRep -- Type is just an ordinary type synonym
Идея заключается в том, что у нас есть новая фундаментальная константа типа TYPE, параметризованная RuntimeRep. Таким образом, мы получаем Int# :: TYPE IntRep и Bool :: TYPE LiftedRep. Значение с типом вида TYPE x может находиться по любую сторону стрелки функции ->. Следовательно, можно сказать, что -> имеет тип TYPE r1 -> TYPE r2 -> TYPE LiftedRep. Результат всегда имеет подъём, поскольку в GHC все функции имеют подъём.
6.4.15.1. Полиморфизм уровня
Особый случай полиморфизма представления — полиморфизм уровня, при котором мы абстрагируемся по переменной вида Levity, например:
example :: forall (l :: Levity) (a :: TYPE (BoxedRep l)). (Int -> a) -> a example f = f 42
С помощью UnliftedDatatypes можно даже объявлять полиморфные по уровню типы данных:
type PEither :: Type -> Type -> TYPE (BoxedRep l) data PEither l r = PLeft l | PRight r
6.4.15.2. Переменные и аргументы не могут быть полиморфными по представлению
Если бы GHC не приходилось компилировать программы, работающие в реальном мире, на этом можно было бы закончить. Но полиморфизм представления может создать немало проблем для генератора кода GHC. Рассмотрим следующий пример:
bad :: forall (r1 :: RuntimeRep) (r2 :: RuntimeRep)
(a :: TYPE r1) (b :: TYPE r2).
(a -> b) -> a -> b
bad f x = f x
Это выглядит как обобщение стандартного оператора $. Однако при рассмотрении компиляции этого выражения в исполняемый код возникают проблемы. В частности, при вызове bad нам нужно каким-то образом передать x в bad. Какова ширина (то есть сколько бит) у x? Это указатель? В какой регистр следует поместить x — регистр с плавающей точкой или целочисленный? На эти вопросы невозможно ответить, поскольку тип x, a :: TYPE r1, полиморфен по представлению. Поэтому такие конструкции запрещены следующим простым правилом:
Переменная не может иметь тип, полиморфный по представлению.
Это исключает bad, поскольку переменная x имела бы тип, полиморфный по представлению.
Однако не всё потеряно. Мы всё ещё можем написать следующее:
good :: forall r (a :: Type) (b :: TYPE r).
(a -> b) -> a -> b
good f x = f x
Здесь полиморфным по представлению является только b. Переменных с типом, полиморфным по представлению, нет. Генератор кода без проблем обрабатывает такой вариант. Тем не менее, можно написать определение с типом bad:
($) :: forall (r1 :: RuntimeRep) (r2 :: RuntimeRep)
(a :: TYPE r1) (b :: TYPE r2).
(a -> b) -> a -> b
($) f = f
Применив eta-сокращение, мы избавились от x и, таким образом, не получили переменную с типом, полиморфным по представлению. Именно таков тип оператора $ в GHC, немного более общий, чем версия из Haskell 98. Однако его свойства строгости отличаются: (good undefined) `seq` () эквивалентно (), тогда как (($) undefined) `seq` () расходится.
Поскольку генератору кода необходимо хранить и перемещать аргументы так же, как и переменные, приведённая выше логика в равной мере относится к аргументам функций: они также не могут быть полиморфными по представлению.
6.4.15.3. Нижние значения, полиморфные по представлению
Полиморфизм представления можно эффективно использовать с error и undefined, типы которых приведены ниже:
undefined :: forall (r :: RuntimeRep) (a :: TYPE r).
HasCallStack => a
error :: forall (r :: RuntimeRep) (a :: TYPE r).
HasCallStack => String -> a
Эти функции не связывают переменную, полиморфную по представлению, поэтому они допустимы. Благодаря полиморфизму их можно удобно использовать для создания заглушек функций, возвращающих неупакованные типы.
6.4.15.4. Вывод типов и значения по умолчанию
GHC не выводит типы, полиморфные по представлению. Если представление переменной не задано, предполагается, что оно равно LiftedRep. Например, если написать f a b = a b, выведенный тип f будет таким:
f :: forall {a :: Type} {b :: Type}. (a -> b) -> a -> b
хотя
f :: forall {rep} {a :: Type} {b :: TYPE rep}. (a -> b) -> a -> b
также было бы допустимо, как описано выше.
Аналогично, в написанной пользователем сигнатуре f :: forall a b. (a -> b) -> a -> b GHC предполагает, что a и b имеют вид Type. Чтобы использовать другое представление, необходимо указать виды a и b.
При выводе типов GHC не квантифицирует по переменным вида RuntimeRep и Levity. Вместо этого им присваиваются значения по умолчанию LiftedRep и Lifted соответственно. Аналогично, переменным Multiplicity (Линейные типы) присваивается значение по умолчанию Many.
6.4.15.5. Вывод типов, полиморфных по представлению
-
-fprint-explicit-runtime-reps -
Выводить параметры
RuntimeRepиLevityв том виде, в котором они указаны; иначе им присваиваются значения по умолчаниюLiftedRepиLiftedсоответственно.
Большинству пользователей GHC не придётся беспокоиться о полиморфизме представления или неупакованных типах. Для них информация о полиморфизме представления в типе $ бесполезна. Поэтому по умолчанию она скрывается: при выводе предполагается, что все переменные типов вида RuntimeRep имеют вид LiftedRep, а TYPE LiftedRep выводится как Type (или *, если включено расширение StarIsType).
Если вы хотите видеть полиморфизм представления в типах, включите флаг -fprint-explicit-runtime-reps. Например:
ghci> :t ($)
($) :: (a -> b) -> a -> b
ghci> :set -fprint-explicit-runtime-reps
ghci> :t ($)
($)
:: forall (r :: GHC.Types.RuntimeRep) a (b :: TYPE r).
(a -> b) -> a -> b