Для обеспечения полной гибкости в использовании типов необходимо использовать систему типов для различения упакованных, поднятых типов (обычных типов, таких как 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.14.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.14.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
С помощью эта-редукции мы избавились от x и, следовательно, у нас нет переменной с типом, полиморфным по представлению. Фактически, это истинный тип оператора GHC $ , немного более общий, чем версия Haskell 98. Однако, его свойства строгости отличаются: (good undefined) `seq` () эквивалентно (), в то время как (($) undefined) `seq` () расходится.
Поскольку генератор кода должен хранить и перемещать аргументы, а также переменные, приведенная выше логика также применима к аргументам функций, которые не могут быть полиморфными по представлению.
6.4.14.3. Полиморфные по представлению «дно»
Мы можем использовать полиморфизм представлений с хорошим эффектом с error и undefined , типы которых приведены здесь:
undefined :: forall (r :: RuntimeRep) (a :: TYPE r).
HasCallStack => a
error :: forall (r :: RuntimeRep) (a :: TYPE r).
HasCallStack => String -> a
Эти функции не связывают переменную, полиморфную по представлению, и поэтому принимаются. Их полиморфизм позволяет пользователям удобно подменять функции, возвращающие распакованные типы.
6.4.14.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.14.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