GHC.Generics
| Авторские права | (c) Университет Утрехта 2010-2011, Оксфордский университет 2012-2013 |
|---|---|
| Лицензия | см. libraries/base/LICENSE |
| Поддерживающий | libraries@haskell.org |
| Устойчивость | внутренняя |
| Переносимость | непереносимая |
| Безопасный Haskell | Достоверный |
| Язык | Haskell2010 |
Содержание
Описание
Если вы используете GHC.Generics, вам следует рассмотреть использование пакета http://hackage.haskell.org/package/generic-deriving, который содержит много полезных обобщенных функций.
С момента выпуска: 4.6.0.0
Введение
Функции, обобщенные по типу данных, основаны на идее преобразования значений типа данных T в соответствующие значения (почти) изоморфного типа Rep T. Тип Rep T строится из ограниченного набора конструкторов типов, все они предоставляются этим модулем. Функция, обобщенная по типу данных, представляет собой перегруженную функцию с экземплярами для большинства этих конструкторов типов, а также с оберткой, которая выполняет сопоставление между T и Rep T. Используя эту технику, нам нужно всего несколько обобщенных экземпляров для реализации функциональности, которая работает для любого представимого типа.
Представимые типы собираются в классе Generic, который определяет ассоциированный тип Rep и функции преобразования from и to. Как правило, вы не будете определять экземпляры Generic вручную, но заставите компилятор сгенерировать их для вас.
Представление типов данных
Ключ к определению собственных функций, обобщенных по типу данных, заключается в понимании того, как представлять типы данных с использованием данного набора конструкторов типов.
Давайте сначала рассмотрим пример:
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving Generic
Вышеприведенное объявление (которое требует прагмы языка DeriveGeneric) приводит к следующему представлению:
instanceGeneric(Tree a) where typeRep(Tree a) =D1D1Tree (C1C1_0Tree (S1NoSelector(Par0a)):+:C1C1_1Tree (S1NoSelector(Rec0(Tree a)):*:S1NoSelector(Rec0(Tree a)))) ...
Подсказка: Вы можете получить информацию о генерируемом коде из GHC, передав флаг -ddump-deriv. В GHCi вы можете развернуть семейство типов, например, Rep, с помощью команды :kind!
Производные и базовые типы представления
Существует много функций, обобщенных по типу данных, которые не различают позиции параметров и позиции рекурсивных вызовов. Также есть много функций, обобщенных по типу данных, которые вообще не обращают внимания на имена типов данных и конструкторов. Чтобы свести к минимуму количество случаев, которые нужно рассматривать в обобщенных функциях в такой ситуации, оказывается, что многие из конструкторов типов, представленных выше, на самом деле являются синонимами, определяющими их как варианты меньшего набора конструкторов.
Отдельные поля конструкторов: K1
Конструкторы типов Par0 и Rec0 являются вариантами K1
typePar0=K1PtypeRec0=K1R
Здесь P и R — это снова прокси-типы, не имеющие связанных значений.
Метаинформация: M1
Конструкторы типов S1, C1 и D1 — это все варианты M1
typeS1=M1StypeC1=M1CtypeD1=M1D
Типы S, C и D опять же являются прокси-типами, используемыми только для создания нескольких вариантов M1
Дополнительные конструкторы типов обобщенного представления
Помимо K1, M1, :+: и :*: существует несколько других конструкторов типов, которые встречаются в представлениях других типов данных.
Пустые типы данных: V1
Для пустых типов данных используется представление V1. Например,
data Empty deriving Generic
дает
instanceGenericEmpty where typeRepEmpty =D1D1EmptyV1
Конструкторы без полей: U1
Если у конструктора нет аргументов, то используется представление U1. Например, представление Bool — это
instanceGenericBool where typeRepBool =D1D1Bool (C1C1_0BoolU1:+:C1C1_1BoolU1)
Представление типов с множеством конструкторов или полей
Поскольку :+: и :*: — это просто бинарные операторы, можно спросить, что произойдёт, если тип данных имеет более двух конструкторов или конструктор с более чем двумя полями. Ответ прост: операторы используются несколько раз, чтобы комбинировать все конструкторы и поля по мере необходимости. Однако пользователи /не должны полагаться на определённую стратегию вложения/ для :+: и :*:. Компилятор свободен выбирать любое вложение, которое ему нравится. (На практике текущая реализация пытается создать более или менее сбалансированное вложение, так что обход структуры типа данных от корня до конкретного компонента можно выполнить за логарифмическое, а не линейное время.)
Определение функций, обобщенных по типу данных
Функция, обобщенная по типу данных, состоит из двух частей:
- Обобщенные экземпляры для функции, реализующие её для большинства конструкторов типов представления, представленных выше.
- Оборачивающий, который для любого типа данных, который находится в
Generic, выполняет преобразование между исходным значением и его представлением на основеRep, а затем вызывает обобщенные экземпляры.
В качестве примера давайте рассмотрим функцию encode, которая генерирует примитивное, но без потерь, битовое кодирование значений различных типов данных. Таким образом, мы стремимся определить функцию
encode :: Generic a => a -> [Bool]
где мы используем Bool в качестве нашего типа данных для битов.
Для первой части мы определим класс Encode'. Возможно, неожиданно, что этот класс параметризован конструктором типа f с типом * -> *. Это технический момент: все конструкторы типов представления работают со значением * -> * в качестве базового типа. Но аргумент типа никогда не используется. Это может быть изменено в будущем. Класс имеет один метод, и мы используем тип, который мы хотим для нашей конечной функции, но заменяем вхождения обобщенного аргумента типа a на f p (где p — любой аргумент; он не будет использоваться).
class Encode' f where encode' :: f p -> [Bool]
С целью сделать encode работающей с Tree и другими типами данных, мы теперь определяем экземпляры для конструкторов типов представления V1, U1, :+:, :*:, K1 и M1
Определение типов обобщенного представления
Для этого нам нужны фактические определения этих типов:
dataV1p -- lifted version of Empty dataU1p =U1-- lifted version of () data (:+:) f g p =L1(f p) |R1(g p) -- lifted version ofEitherdata (:*:) f g p = (f p):*:(g p) -- lifted version of (,) newtypeK1i c p =K1{unK1:: c } -- a container for a c newtypeM1i t f p =M1{unM1:: f p } -- a wrapper
Итак, U1 — это просто тип единицы, :+: — это просто двоичный выбор, подобный Either, :*: — это двоичная пара, подобная конструктору пар (,), а K1 — это значение определенного типа c, и M1 оборачивает значение обобщенного аргумента типа, что в поднятом мире является f p (где нас не интересует p).
Обобщенные экземпляры
Экземпляр для V1 немного нелепый (но и редко используется):
instance Encode' V1 where
encode' x = undefined
Нет значений типа V1 p для передачи (кроме undefined), поэтому это фактически невозможно. Можно спросить, зачем вообще полезно определять экземпляр для V1 в этом случае? Ну, пустой тип может использоваться как аргумент для непустого типа, и вы всё ещё можете захотеть закодировать полученный тип. Как пример, рассмотрим [Empty], который не является пустым типом, но содержит только пустой список. Экземпляр V1 гарантирует, что мы можем вызвать обобщенную функцию для таких типов.
Существует ровно одно значение типа U1, поэтому его кодирование не требует знаний, и мы можем использовать ноль битов:
instance Encode'U1where encode'U1= []
В случае :+: мы получаем False или True в зависимости от того, находится ли конструктор предоставленного значения слева или справа:
instance (Encode' f, Encode' g) => Encode' (f:+:g) where encode' (L1x) = False : encode' x encode' (R1x) = True : encode' x
В случае :*: мы добавляем кодирования двух подкомпонентов:
instance (Encode' f, Encode' g) => Encode' (f:*:g) where encode' (x:*:y) = encode' x ++ encode' y
Случай для K1 довольно интересный. Здесь мы рекурсивно вызываем конечную функцию encode, которую ещё нужно определить. Мы будем использовать другой класс типов Encode для этой функции:
instance (Encode c) => Encode' (K1i c) where encode' (K1x) = encode x
Обратите внимание, как Par0 и Rec0 оба сопоставляются с K1, что позволяет нам определить здесь унифицированный экземпляр.
Аналогичным образом, мы можем определить унифицированный экземпляр для M1, поскольку мы полностью игнорируем всю метаинформацию:
instance (Encode' f) => Encode' (M1i t f) where encode' (M1x) = encode' x
В отличие от K1, экземпляр для M1 ссылается на encode', а не на encode.
Обертка и обобщенное значение по умолчанию
Теперь мы определяем класс Encode для фактической функции encode:
class Encode a where encode :: a -> [Bool] default encode :: (Generica) => a -> [Bool] encode x = encode' (fromx)
Приходящее значение x преобразуется с помощью from, затем мы выполняем переадресацию к универсальным экземплярам с помощью encode'. Мы используем это как стандартное определение для encode. Нам нужна сигнатура «стандартного кодирования», потому что обычные стандартные методы Haskell не должны вводить дополнительные ограничения класса, но наш универсальный стандарт — делает.
Определение конкретного экземпляра теперь так же просто, как сказать
instance (Encode a) => Encode (Tree a)
Опускание универсальных экземпляров
Не всегда требуется предоставлять экземпляры для всех типов универсального представления, но пропуск экземпляров ограничивает набор типов данных, для которых будут работать функции:
- Если не указан экземпляр
:+:, функция может по-прежнему работать с пустыми типами данных или типами данных, у которых есть единственный конструктор, но не будет работать с типами данных, имеющими более одного конструктора. - Если не указан экземпляр
:*:, функция может по-прежнему работать с типами данных, где каждый конструктор имеет только ноль или один параметр, в частности, для типов перечисления. - Если не указан экземпляр
K1, функция может по-прежнему работать с типами перечисления, где ни один конструктор не имеет параметров. - Если не указан экземпляр
V1, функция может по-прежнему работать с любым непустым типом данных. - Если не указан экземпляр
U1, функция может по-прежнему работать с любым типом данных, где каждый конструктор имеет по крайней мере один параметр.
Экземпляр M1 всегда требуется (но он может просто игнорировать метаинформацию, как в случае с encode выше).
Универсальные классы конструкторов
Определенные выше функции, работающие с типами данных, подходят для большого класса типов данных, включая параметризованные типы данных. (Мы использовали Tree в качестве примера выше, который относится к типу * -> *.) Однако класс Generic охватывает типы с видом *, и поэтому полученные универсальные функции (например, encode) должны быть параметризованы универсальным аргументом типа с видом *.
Что делать, если нужно определить универсальные классы, которые охватывают конструкторы типов (такие как Functor, Traversable или Foldable)?
Класс Generic1
Подобно Generic, существует класс Generic1, который определяет представление Rep1 и функции преобразования from1 и to1, только Generic1 охватывает типы с видом * -> * Класс Generic1 также выводим.
Представление Rep1 немного отличается от Rep. Давайте рассмотрим Tree ещё раз в качестве примера:
data Tree a = Leaf a | Node (Tree a) (Tree a)
deriving Generic1
Вышеуказанное объявление приводит к генерации следующего представления:
instance Generic1 Tree where type Rep1 Tree = D1 D1Tree (C1 C1_0Tree (S1 NoSelector Par1) :+: C1 C1_1Tree (S1 NoSelector (Rec1 Tree) :*: S1 NoSelector (Rec1 Tree))) ...
Представление повторно использует D1, C1, S1 (и, следовательно, M1 ), а также :+: и :*: из Rep. (Эта возможность повторного использования является причиной того, что мы используем фиктивный аргумент типа для типов с видом kind-* , но и без того участвуют достаточно разные имена, чтобы не дублировать каждый из них.)
Отличие в том, что мы теперь используем Par1 для ссылки на параметр (а этот параметр, который раньше был a ), нигде не упоминается по имени; и мы используем Rec1 для ссылки на рекурсивное использование Tree a.
Представление типов * -> *
В отличие от Par0 и Rec0, конструкторы типов Par1 и Rec1 не отображаются на K1. Они определяются непосредственно, как показано ниже:
newtypePar1p =Par1{unPar1:: p } -- gives access to parameter p newtypeRec1f p =Rec1{unRec1:: f p } -- a wrapper
В Par1 параметр p используется впервые, тогда как Rec1 просто оборачивает применение f к p.
Обратите внимание, что K1 (в виде Rec0 ) может все еще встречаться в представлении Rep1 , а именно, когда тип данных имеет параметр, который не упоминает параметр.
Объявление
data WithInt a = WithInt Int a
deriving Generic1
дает
classRep1WithInt where typeRep1WithInt =D1D1WithInt (C1C1_0WithInt (S1NoSelector(Rec0Int):*:S1NoSelectorPar1))
Если параметр a появляется под композицией других конструкторов типов, то представление также включает композицию:
data Rose a = Fork a [Rose a]
дает
classRep1Rose where typeRep1Rose =D1D1Rose (C1C1_0Rose (S1NoSelectorPar1:*:S1NoSelector([]:.:Rec1Rose)
где
newtype (:.:) f g p =Comp1{unComp1:: f (g p) }
Универсальные типы представления
Пустое значение: используется для типов данных без конструкторов
Единица: используется для конструкторов без аргументов
Конструкторы
| U1 |
Используется для маркировки вхождений параметра
Экземпляры
Рекурсивные вызовы вида * -> *
Экземпляры
Примеры использования
Метаинформация (имена конструкторов и т. д.)
Примеры использования
data (f :+: g) p infixr 5 Источник
Суммы: кодирование выбора между конструкторами
Примеры использования
data (f :*: g) p infixr 6 Источник
Произведения: кодирование нескольких аргументов конструкторов
Конструкторы
| (f p) :*: (g p) infixr 6 |
Примеры использования
newtype (f :.: g) p infixr 7 Источник
Композиция функторов
Примеры использования
Синонимы для удобства
Синоним типа для кодирования рекурсии (типа *)
Синоним типа для кодирования параметров (кроме последнего)
Тег для K1: рекурсия (типа *)
Тег для K1: параметры (кроме последнего)
Синоним типа для кодирования метаинформации для типов данных
Синоним типа для кодирования метаинформации для конструкторов
Синоним типа для кодирования метаинформации для селекторов записей
Тег для M1: тип данных
Метка для M1: конструктор
Метка для M1: селектор записи
Метаинформация
Класс для типов данных, представляющих типы данных
Минимальное полное определение
Методы
datatypeName :: t d (f :: * -> *) a -> [Char] Source
Имя типа данных (без квалификатора)
moduleName :: t d (f :: * -> *) a -> [Char] Source
Полное имя модуля, в котором объявлен тип
isNewtype :: t d (f :: * -> *) a -> Bool Source
Помечает, является ли тип данных в действительности новым типом
class Constructor c where Source
Класс для типов данных, представляющих конструкторы данных
Минимальное полное определение
Методы
conName :: t c (f :: * -> *) a -> [Char] Source
Имя конструктора
conFixity :: t c (f :: * -> *) a -> Fixity Source
Фиксированность конструктора
conIsRecord :: t c (f :: * -> *) a -> Bool Source
Помечает, является ли этот конструктор записью
Класс для типов данных, представляющих записи
Экземпляры
data NoSelector Source
Используется для полей конструктора без имени
Экземпляры
Тип данных для представления фиксированности конструктора. Объявление infix | напрямую соответствует применению Infix.
Конструкторы
| Prefix | |
| Infix Associativity Int |
data Associativity Source
Тип данных для представления ассоциативности конструктора
Конструкторы
| LeftAssociative | |
| RightAssociative | |
| NotAssociative |
Экземпляры
Тип данных для представления арности кортежа.
Получить приоритет значения фиксированности.
Обобщенные классы типов
Типы, представляемые с типом * . Этот класс может быть выведен в GHC с флагом DeriveGeneric.
Методы
Преобразование из типа данных в его представление
Преобразование из представления в тип данных
Экземпляры
| Generic Bool | |
| Generic Char | |
| Generic Double | |
| Generic Float | |
| Generic Int | |
| Generic Ordering | |
| Generic () | |
| Generic Associativity | |
| Generic Fixity | |
| Generic Arity | |
| Generic Any | |
| Generic All | |
| Generic Void | |
| Generic [a] | |
| Generic (U1 p) | |
| Generic (Par1 p) | |
| Generic (Maybe a) | |
| Generic (Last a) | |
| Generic (First a) | |
| Generic (Product a) | |
| Generic (Sum a) | |
| Generic (Endo a) | |
| Generic (Dual a) | |
| Generic (ZipList a) | |
| Generic (Identity a) | |
| Generic (Either a b) | |
| Generic (Rec1 f p) | |
| Generic (a, b) | |
| Generic (Proxy * t) | |
| Generic (WrappedMonad m a) | |
| Generic (Const a b) | |
| Generic (K1 i c p) | |
| Generic ((:+:) f g p) | |
| Generic ((:*:) f g p) | |
| Generic ((:.:) f g p) | |
| Generic (a, b, c) | |
| Generic (Alt k f a) | |
| Generic (WrappedArrow a b c) | |
| Generic (M1 i c f p) | |
| Generic (a, b, c, d) | |
| Generic (a, b, c, d, e) | |
| Generic (a, b, c, d, e, f) | |
| Generic (a, b, c, d, e, f, g) |
Представимые типы рода * -> *. Этот класс выводим в GHC с флагом DeriveGeneric.
Примеры использования
| Generic1 [] | |
| Generic1 МожетБыть | |
| Generic1 Последний | |
| Generic1 Первый | |
| Generic1 Произведение | |
| Generic1 Сумма | |
| Generic1 Дуальный | |
| Generic1 ZipList | |
| Generic1 Тождество | |
| Generic1 (Either a) | |
| Generic1 ((,) a) | |
| Generic1 (WrappedMonad m) | |
| Generic1 (Const a) | |
| Generic1 ((,,) a b) | |
| Generic1 (Alt * f) | |
| Generic1 (WrappedArrow a b) | |
| Generic1 ((,,,) a b c) | |
| Generic1 ((,,,,) a b c d) | |
| Generic1 ((,,,,,) a b c d e) | |
| Generic1 ((,,,,,,) a b c d e f) |
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/7.10.3/docs/html/libraries/base-4.8.2.0/GHC-Generics.html