Spec-Zone.ru › Haskell 9

6.4.15. Полиморфизм представления

Чтобы обеспечить полную гибкость в использовании видов, необходимо использовать систему видов для различения упакованных типов с подъёмом (обычных повседневных типов, таких как 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

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.14.1/docs/users_guide/exts/representation_polymorphism.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API