Объявление экземпляра имеет вид
instance (assertion1, ..., assertionn) => class type1 ... typem where ...
Часть перед «=>» — это контекст, а часть после «=>» — заголовок объявления экземпляра.
Когда GHC пытается разрешить, например, ограничение C Int Bool, он пытается сопоставить с ним каждое объявление экземпляра, подставляя значения в заголовок объявления экземпляра. Рассмотрим следующие объявления:
instance context1 => C Int a where ... -- (A) instance context2 => C a Bool where ... -- (B)
По умолчанию GHC требует, чтобы ограничению, которое он пытается разрешить, соответствовал ровно один экземпляр. Например, ограничение C Int Bool соответствует экземплярам (A) и (B), поэтому оно будет отклонено; тогда как C Int Char соответствует только (A), поэтому будет выбран (A).
Обратите внимание:
- При сопоставлении GHC не учитывает контекст объявления экземпляра (
context1и т. д.). - Допускается возможность перекрытия (например, при наличии обоих объявлений (A) и (B)); ошибка сообщается, только если конкретному ограничению соответствует более одного экземпляра.
Флаги, смягчающие правила разрешения экземпляров, см. также в разделе Перекрывающиеся экземпляры.
6.8.8.1. Ослабленные правила для заголовка экземпляра
-
TypeSynonymInstances -
- Подразумевается расширением:
- Добавлено в версии:
-
6.8.1
- Статус:
Разрешает определять экземпляры классов типов для синонимов типов.
-
FlexibleInstances -
- Включает:
- Добавлено в версии:
-
6.8.1
- Статус:
Разрешает определять экземпляры классов типов с произвольными вложенными типами в заголовке экземпляра.
В Haskell 2010 заголовок объявления экземпляра должен иметь вид C (T a1 ... an), где C — класс, T — конструктор типа данных, а a1 ... an — различные переменные типов. В случае многопараметрических классов типов это правило применяется к каждому параметру заголовка экземпляра (возможно, было бы достаточно, чтобы такую форму имел лишь один параметр, а остальные были переменными типов, но пока действуют именно эти правила).
GHC ослабляет это правило двумя способами:
-
Расширение
TypeSynonymInstancesразрешает использовать в заголовках экземпляров синонимы типов. Как обычно, использование синонима типа — это просто сокращённая запись правой части определения этого синонима. Например:type Point a = (a,a) instance C (Point a) where ...
допустимо. Это объявление экземпляра эквивалентно
instance C (a,a) where ...
Как всегда, синонимы типов должны быть полностью применены. Например, нельзя написать:
instance Monad Point where ...
-
Расширение
FlexibleInstancesразрешает указывать произвольные вложенные типы в заголовке объявления экземпляра. Например, следующее объявление экземпляра становится допустимым:instance C (Maybe Int) where ...
См. также правила перекрытия.
Расширение
FlexibleInstancesподразумеваетTypeSynonymInstances.
Однако объявление экземпляра по-прежнему должно соответствовать правилам завершения экземпляров: см. раздел Правила завершения экземпляров.
6.8.8.2. Формальный синтаксис типов в объявлениях экземпляров
В начале объявления экземпляра разрешены только очень конкретные формы типов. Чтобы точнее определить, какие формы типов допустимы, а какие нет, ниже мы приводим грамматику в стиле BNF для начала объявлений экземпляров.
inst_top ::= 'instance' opt_forall opt_ctxt inst_head opt_where
opt_forall ::= <empty>
| 'forall' tv_bndrs '.'
tv_bndrs ::= <empty>
| tv_bndr tv_bndrs
tv_bndr ::= tyvar
| '(' tyvar '::' ctype ')'
opt_ctxt ::= <empty>
| btype '=>'
| '(' ctxt ')' '=>'
ctxt ::= ctype
| ctype ',' ctxt
inst_head ::= '(' inst_head ')'
| prefix_cls_tycon arg_types
| arg_type infix_cls_tycon arg_type
| '(' arg_type infix_cls_tycon arg_type ')' arg_types
arg_types ::= <empty>
| arg_type arg_types
opt_where ::= <empty>
| 'where'
Здесь:
-
btype— это тип, у которого внешний уровень не может содержатьforall/=>, если они не заключены в круглые скобки. Например,forall a. aиEq a => aне являются допустимымиbtype, а(forall a. a)и(Eq a => a)допустимы. -
ctype— этоbtype, для которого нет ограничений наforall/=>внешнего уровня, поэтомуforall a. aиEq a => aявляются допустимымиctype. -
arg_type— это тип, в котором не допускаютсяforallи=> -
prefix_cls_tycon— это конструктор типа класса, записанный в префиксной форме (например,Showили(&&&)), аinfix_cls_tycon— конструктор типа класса, записанный в инфиксной форме (например,\`Show\`или&&&).
Это упрощённая грамматика, которая не охватывает все детали реализации парсера GHC (например, расположение комментариев Haddock), но её достаточно, чтобы понять, что разрешено синтаксисом. Некоторые дополнительные замечания об этой грамматике:
-
В объявлениях экземпляров не допускаются вложенные
forallили=>. Например, следующее будет отклонено:instance forall a. forall b. C (Either a b) where ...
Поэтому
inst_topуказывает квантификацию и ограничения в начале с помощьюopt_forallиopt_context. -
Кроме того, типы в объявлениях экземпляров не допускают внешние круглые скобки, охватывающие
opt_forallилиopt_ctxt, если используется хотя бы один из них. Например,instance (forall a. C a)будет отклонено, так как GHC сочтётforallвложенным.Обратите внимание, что скобки в
inst_headдопустимы. Например, принимаетсяinstance (C a), как иinstance forall a. (C a).
6.8.8.3. Правила завершения экземпляров
Независимо от того, включены ли расширения FlexibleInstances и FlexibleContexts, объявления экземпляров должны соответствовать некоторым правилам, которые гарантируют завершение разрешения экземпляров. Ограничения можно снять с помощью UndecidableInstances (см. раздел Неразрешимые экземпляры и зацикленные суперклассы).
Правила таковы:
-
Условия Патерсона: для каждого ограничения класса
(C t1 ... tn)в контексте:- Ни одна переменная типа не встречается в ограничении чаще, чем в заголовке.
- В ограничении меньше конструкторов и переменных (суммарно и с учётом повторений), чем в заголовке.
- В ограничении не упоминаются функции типов. Применение функции типа в принципе может развернуться в тип произвольного размера, поэтому такие ограничения безусловно отклоняются.
Если эти три условия выполняются, мы говорим, что ограничение
(C t1 ... tn)меньше по Патерсону, чем заголовок экземпляра. - Условие покрытия. Для каждой функциональной зависимости ⟨tvs⟩left
->⟨tvs⟩right класса каждая переменная типа в S(⟨tvs⟩right) должна присутствовать в S(⟨tvs⟩left), где S — подстановка, отображающая каждую переменную типа в объявлении класса на соответствующий тип в заголовке экземпляра.
Эти ограничения гарантируют завершение разрешения экземпляров: каждый шаг редукции уменьшает задачу как минимум на один конструктор. Много справочной информации о причинах этих ограничений можно найти в статье Понимание функциональных зависимостей с помощью правил обработки ограничений.
Например, следующие варианты допустимы:
instance C Int [a] -- Multiple parameters
instance Eq (S [a]) -- Structured type in head
-- Repeated type variable in head
instance C4 a a => C4 [a] [a]
instance Stateful (ST s) (MutVar s)
-- Head can consist of type variables only
instance C a
instance (Eq a, Show b) => C2 a b
-- Non-type variables in context
instance Show (s a) => Show (Sized s a)
instance C2 Int a => C3 Bool [a]
instance C2 Int a => C3 [a] b
А следующие — нет:
-- Context assertion no smaller than head
instance C a => C a where ...
-- (C b b) has more occurrences of b than the head
instance C b b => Foo [b] where ...
Те же ограничения применяются к экземплярам, порождённым конструкциями deriving. Поэтому следующий вариант принимается:
data MinHeap h a = H a (h a) deriving (Show)
поскольку выведенный экземпляр
instance (Show a, Show (h a)) => Show (MinHeap h a)
соответствует приведённым выше правилам.
Ограничения на функциональные зависимости (Функциональные зависимости) особенно проблематичны. Возникает соблазн вводить в контекст переменные типов, отсутствующие в заголовке, что запрещено обычными правилами. Например:
class HasConverter a b | a -> b where convert :: a -> b data Foo a = MkFoo a instance (HasConverter a b,Show b) => Show (Foo a) where show (MkFoo value) = show (convert value)
Однако это рискованно. Вот, например, программа, которая заставит проверку типов зациклиться:
class D a class F a b | a->b instance F [a] [[a]] instance (D c, F a c) => D [a] -- 'c' is not mentioned in the head
Также может возникнуть соблазн ослабить условие покрытия:
class Mul a b c | a b -> c where (.*.) :: a -> b -> c instance Mul Int Int Int where (.*.) = (*) instance Mul Int Float Float where x .*. y = fromIntegral x * y instance Mul a b c => Mul a [b] [c] where x .*. v = map (x.*.) v
Третье объявление экземпляра не удовлетворяет условию покрытия; и действительно, следующее (довольно странное) определение:
f = \ b x y -> if b then x .*. [y] else y
приводит к зацикливанию при выводе экземпляров, поскольку требует ограничение (Mul a [b] b).
6.8.8.4. Неразрешимые экземпляры и зацикленные суперклассы
-
UndecidableInstances -
- Добавлено в версии:
-
6.8.1
Разрешает определять экземпляры, которые могут привести к незавершению проверки типов.
Расширение UndecidableInstances снимает ограничения на объявления экземпляров, описанные в разделе Правила завершения экземпляров. Расширение UndecidableInstances также снимает некоторые ограничения, накладываемые на экземпляры семейств типов; см. раздел Разрешимость экземпляров синонимов типов.
Расширение UndecidableInstances позволяет создать цикл суперклассов, который приводит к незавершению программы. Чтобы этого избежать, GHC накладывает правила на то, как удовлетворяются ограничения суперклассов в объявлении экземпляра. Эти правила применяются даже при включённом расширении UndecidableInstances. Рассмотрим:
class C a => D a where ... instance Wombat [b] => D [b] where ...
При проверке типов этого объявления instance GHC должен убедиться, что удовлетворено ограничение суперкласса (C [b]) для D. Мы называем (C [b]) запрошенным ограничением суперкласса объявления экземпляра.
Если имеется instance blah => C [b], что часто бывает, GHC может использовать объявление экземпляра, и всё будет хорошо. Но предположим, такого экземпляра нет, и GHC может удовлетворить запрошенное (C [b]) только за счёт контекста экземпляра, а именно заданного ограничения (Wombat [b]). Возможно, объявление Wombat выглядит так:
class C a => Wombat a
Таким образом, у заданного (Wombat [b]) есть суперкласс (C [b]), и кажется, что мы можем удовлетворить запрошенное ограничение (C [b]) этим суперклассом Wombat. Однако выясняется, что это может привести к незаметным зацикливаниям словарей, поэтому GHC это запрещает.
Правило таково: запрошенное ограничение суперкласса можно удовлетворить только одним из следующих трёх способов:
-
Непосредственно из контекста объявления экземпляра. Например, если бы объявление выглядело так:
instance (Wombat [b], C [b]) => D [b] where ...
мы могли бы удовлетворить запрошенное
(C [b])заданным(C [b]). -
С помощью другого объявления экземпляра. Например, если бы у нас было:
instance C b => C [b] where ...
мы могли бы удовлетворить запрошенное ограничение суперкласса
(C [b])с помощью этого экземпляра, сведя его к запрошенному ограничению(C b)(которое всё ещё необходимо разрешить). -
С помощью непосредственного суперкласса заданного ограничения X, которое меньше по Патерсону, чем заголовок объявления экземпляра. Правила для «меньше по Патерсону» в точности описаны в разделе Правила завершения экземпляров:
- Ни одна переменная типа не может встречаться в X чаще, чем в заголовке экземпляра.
- В X должно быть меньше конструкторов типов и переменных (суммарно и с учётом повторений), чем в заголовке экземпляра.
- В X не должны упоминаться функции типов.
Правило (3) — самое сложное. Вот пример из исходного кода самого GHC:
class Ord r => UserOfRegs r a where ... (i1) instance UserOfRegs r a => UserOfRegs r (Maybe a) where (i2) instance (Ord r, UserOfRegs r CmmReg) => UserOfRegs r CmmExpr where
Для (i1) мы можем получить суперкласс (Ord r) посредством выбора из (UserOfRegs r a), поскольку он (то есть UserOfRegs r a) меньше по Патерсону, чем заголовок объявления экземпляра, а именно (UserOfRegs r (Maybe a)).
Но для (i2) это не так: (UserOfRegs r CmmReg) не меньше по Патерсону, чем заголовок экземпляра (UserOfRegs r CmmExpr), поэтому мы не можем использовать суперклассы первого ограничения. Следовательно, вместо этого необходимо добавить в объявление экземпляра явный и, возможно, неожиданный аргумент (Ord r).
Это решение — простое добавление на первый взгляд избыточного ограничения в контекст объявления экземпляра — надёжно: оно всегда устраняет проблему. (Мы рассматривали возможность добавлять его автоматически, но решили, что лучше указывать его явно.)
У исправления этого тонкого цикла суперклассов долгая история; если вам интересно, прочитайте Note [Recursive superclasses] и Note [Solving superclass constraints] в GHC.Tc.TyCl.Instance.
6.8.8.5. Перекрывающиеся экземпляры
-
OverlappingInstances -
- Добавлено в версии:
-
6.8.1
- Статус:
-
Устарело
Устаревшее расширение, ослабляющее проверки, предназначенные для обеспечения завершения разрешения экземпляров. Вместо него используйте прагмы
OVERLAPPING,OVERLAPPABLEилиOVERLAPS.
-
IncoherentInstances -
- Включает:
- Добавлено в версии:
-
6.8.1
- Статус:
-
Устарело
Устаревшее расширение, ослабляющее проверки, предназначенные для обеспечения завершения разрешения экземпляров. Вместо него используйте прагмы
INCOHERENT. Также разрешает классам иметь роли, отличные от номинальных, и влияет на алгоритм разрешения экземпляров для заданных ограничений в области видимости.
Как правило, как обсуждалось в разделе Объявления экземпляров и разрешение, GHC требует, чтобы для разрешения ограничения класса типов было однозначно понятно, какое объявление экземпляра следует использовать. GHC также позволяет ослабить правила разрешения экземпляров, разрешая соответствие более чем одному экземпляру, если среди них есть наиболее специфичный. Кроме того, ограничения можно ослабить ещё сильнее, разрешив соответствие нескольких экземпляров независимо от наличия наиболее специфичного. В этом разделе подробно описаны эти правила.
Чтобы управлять выбором экземпляра, можно указать поведение при перекрытии для отдельных экземпляров с помощью прагмы, записываемой сразу после ключевого слова instance. Прагма может быть одной из следующих: {-# OVERLAPPING #-}, {-# OVERLAPPABLE #-}, {-# OVERLAPS #-} или {-# INCOHERENT #-}.
На поведение сопоставления также влияют два флага расширений языка на уровне модуля: OverlappingInstances и IncoherentInstances. Эти расширения устарели (начиная с GHC 7.10) и заменены более точными прагмами для отдельных экземпляров.
Более точная спецификация такова. Готовность к перекрытию или несогласованности — свойство самого объявления экземпляра, которое задаётся следующим образом:
- Экземпляр является несогласованным, если у него есть прагма
INCOHERENT; либо если у экземпляра нет прагмы и он находится в модуле, скомпилированном с расширениемIncoherentInstances. - Экземпляр допускает перекрытие, если у него есть прагма
OVERLAPPABLEилиOVERLAPS; либо если у экземпляра нет прагмы и он находится в модуле, скомпилированном с расширениемOverlappingInstances; либо если экземпляр несогласованный. - Экземпляр является перекрывающимся, если у него есть прагма
OVERLAPPINGилиOVERLAPS; либо если у экземпляра нет прагмы и он находится в модуле, скомпилированном с расширениемOverlappingInstances; либо если экземпляр несогласованный.
Предположим, что в некотором клиентском модуле мы ищем экземпляр для целевого ограничения (C ty1 .. tyn). Поиск выполняется следующим образом:
- Если в области видимости есть заданные ограничения, которые могут соответствовать целевому ограничению (после унификации любых метапеременных), а расширение
IncoherentInstancesне включено, поиск завершается неудачей. - Найти все экземпляры \(I\), которые сопоставляются с целевым ограничением, то есть целевое ограничение является экземпляром \(I\) относительно подстановки. Эти объявления экземпляров называются кандидатами.
- Если кандидатов нет, поиск завершается неудачей.
-
Исключить любого кандидата \(IX\), для которого существует другой кандидат \(IY\), удовлетворяющий обоим следующим условиям:
- \(IY\) строго более специфичен, чем \(IX\). То есть \(IY\) является экземпляром \(IX\) относительно подстановки, но обратное неверно.
- \(IX\) допускает перекрытие или \(IY\) является перекрывающимся. Такой выбор «или», а не «и», позволяет клиенту намеренно переопределить экземпляр из библиотеки, не требуя изменять саму библиотеку.
- Если все оставшиеся кандидаты несогласованные, поиск завершается успешно и возвращает произвольного оставшегося кандидата.
- Если осталось более одного согласованного кандидата, поиск завершается неудачей.
- В противном случае остаётся ровно один согласованный кандидат; назовём его «основным кандидатом».
- Теперь найти все экземпляры, которые унифицируются с целевым ограничением, но не сопоставляются с ним. Такие экземпляры, не являющиеся кандидатами, могут сопоставиться с ограничением после дальнейшей подстановки. Если все они — несогласованные экземпляры верхнего уровня, поиск завершается успешно и возвращает основного кандидата. В противном случае поиск завершается неудачей.
Обратите внимание, что на эти правила не влияют настройки флагов клиентского модуля, где экземпляры используются. Благодаря этим правилам автор библиотеки может создать библиотеку, опирающуюся на перекрывающиеся экземпляры, не требуя от клиента знать об этом.
Об ошибках сообщается лениво (при попытке разрешить ограничение), а не немедленно (при определении самих экземпляров). Рассмотрим, например:
instance C Int b where .. instance C a Bool where ..
Эти экземпляры потенциально перекрываются, но GHC не будет жаловаться на сами объявления экземпляров независимо от настроек флагов. Если позднее мы попытаемся разрешить ограничение (C Int Char), сопоставится только первый экземпляр, и всё будет хорошо. То же относится к (C Bool Bool). Но если мы попытаемся разрешить (C Int Bool), сопоставятся оба экземпляра и будет сообщено об ошибке.
В качестве более содержательного примера работы этих правил рассмотрим:
instance {-# OVERLAPPABLE #-} context1 => C Int b where ... -- (A)
instance {-# OVERLAPPABLE #-} context2 => C a Bool where ... -- (B)
instance {-# OVERLAPPABLE #-} context3 => C a [b] where ... -- (C)
instance {-# OVERLAPPING #-} context4 => C Int [Int] where ... -- (D)
(Для всех этих примеров требуется расширение FlexibleInstances.) Предположим, что механизму вывода типов нужно разрешить ограничение C Int [Int]. Это ограничение соответствует экземплярам (A), (C) и (D), но последний из них более специфичен, поэтому будет выбран именно он.
Если бы (D) не существовал, соответствие по-прежнему имели бы (A) и (C), но ни один из них не был бы наиболее специфичным. В этом случае программа была бы отклонена, если только не включено расширение IncoherentInstances; при его включении программа принимается, а (A) или (C) выбирается произвольно.
Объявление экземпляра является более специфичным, чем другое, тогда и только тогда, когда заголовок первого является экземпляром второго относительно подстановки. Например, (D) более специфичен, чем (C), потому что из (C) можно получить (D), подставив a := Int и b := Int.
Последний пункт (об унифицируемых экземплярах) заставляет GHC проявлять осторожность, прежде чем фиксировать выбор перекрывающегося экземпляра. Например:
f :: [b] -> [b] f x = ...
Предположим, что из правой части f мы получаем ограничение C b [b]. Однако GHC не выбирает окончательно экземпляр (C), поскольку при конкретном вызове f значение b может быть подставлено как Int, и тогда ещё более специфичным окажется экземпляр (D). Поэтому GHC отклоняет программу.
Однако если включить расширение IncoherentInstances при компиляции модуля, содержащего (D), GHC вместо этого выберет (C), не сообщая о проблеме с последующими подстановками.
Обратите внимание: мы указали сигнатуру типа для f, поэтому GHC должен был проверить, что f имеет указанный тип. Предположим вместо этого, что мы не указываем сигнатуру типа и просим GHC вывести её. В этом случае GHC не станет упрощать ограничение C Int [b] (по той же причине, что и раньше), но вместо отклонения программы выведет тип
f :: C b [b] => [b] -> [b]
Так вопрос о том, какой экземпляр выбрать, откладывается до места вызова f, к которому моменту становится больше известно о типе b. Для этого потребуется расширение FlexibleContexts.
Точно такая же ситуация может возникнуть непосредственно в объявлениях экземпляров. Предположим, у нас есть
class Foo a where f :: a -> a instance Foo [b] where f x = ...
и, как и раньше, ограничение C Int [b] возникает из правой части f. GHC отклонит экземпляр, сообщив, что не знает, как разрешить ограничение C Int [b], поскольку ему соответствует более одного объявления экземпляра. Решение — отложить выбор, добавив ограничение в контекст объявления экземпляра:
instance C Int [b] => Foo [b] where f x = ...
(Для этого потребуется расширение FlexibleContexts.)
В качестве примера «заданных ограничений в области видимости» из первого пункта рассмотрим:
instance C a Int g :: forall b c. C b Int => blah g = ...needs (C c Int)...
Здесь GHC не будет разрешать ограничение (C c Int) с помощью экземпляра верхнего уровня, поскольку при конкретном вызове g значения b и c могут быть подставлены одним и тем же типом, что позволит разрешить ограничение другим способом. Это последнее ограничение введено главным образом для обеспечения полноты решателя ограничений. (Заинтересованные читатели могут прочитать Note [Instance and Given overlap] в GHC.Tc.Solver.Dict.) Его легко избежать: в сигнатуре типа не следует указывать ограничение, соответствующее экземпляру верхнего уровня. Флаг -Wsimplifiable-class-constraints выдаёт предупреждение о таких сигнатурах.
Предупреждение
Перекрывающиеся экземпляры следует использовать осторожно. Они могут приводить к несогласованности (то есть в разных частях программы выбираются разные экземпляры) даже без расширения IncoherentInstances. Рассмотрим:
{-# LANGUAGE OverlappingInstances #-}
module Help where
class MyShow a where
myshow :: a -> String
instance MyShow a => MyShow [a] where
myshow xs = concatMap myshow xs
showHelp :: MyShow a => [a] -> String
showHelp xs = myshow xs
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}
module Main where
import Help
data T = MkT
instance MyShow T where
myshow x = "Used generic instance"
instance MyShow [T] where
myshow xs = "Used more specific instance"
main = do { print (myshow [MkT]); print (showHelp [MkT]) }
В функции showHelp GHC не видит перекрывающихся экземпляров и без предупреждений использует экземпляр MyShow [a]. При вызове myshow в main GHC разрешает ограничение MyShow [T] с помощью перекрывающегося объявления экземпляра из модуля Main. В результате программа выводит
"Used more specific instance" "Used generic instance"
(Другим возможным поведением, которое пока не реализовано, было бы отклонение модуля Help на том основании, что более позднее объявление экземпляра может перекрываться с локальным.)
Предупреждение
Оптимизатор GHC (в частности, параметр -fspecialise) предполагает согласованность классов типов и поэтому может заменить любой аргумент-словарь класса типов другим словарём того же типа.
Это может приводить к неожиданным результатам при возникновении несогласованности из-за несогласованных или перекрывающихся экземпляров, если между экземплярами есть наблюдаемая разница (примеры см. в #22448 и #24924).
Параметр -fno-specialise-incoherents отключает специализацию при наличии некоторых несогласованных совпадений экземпляров, что может помочь избежать этой проблемы ценой снижения производительности во время выполнения. Кроме того, специализацию можно полностью отключить с помощью -fno-specialise.