-
StandaloneDeriving -
Разрешает использовать отдельные объявления
deriving.
GHC разрешает отдельные объявления deriving, включаемые с помощью StandaloneDeriving:
data Foo a = Bar a | Baz String deriving instance Eq a => Eq (Foo a)
Синтаксис идентичен синтаксису обычного объявления экземпляра, за исключением (a) ключевого слова deriving и (b) отсутствия части where.
Однако отдельный вывод экземпляров имеет несколько важных отличий от предложения deriving:
- Отдельное объявление вывода экземпляра не обязательно должно находиться в том же модуле, что и объявление типа данных. (Но учитывайте опасность осиротевших экземпляров (Осиротевшие модули и объявления экземпляров).
-
В большинстве случаев необходимо явно указать контекст (в примере это
(Eq a)), точно так же, как в обычном объявлении экземпляра. (В отличие от этого, в предложенииderiving, присоединённом к объявлению типа данных, контекст выводится автоматически.)Исключение из этого правила: контекст отдельного объявления вывода экземпляра может быть выведен автоматически, если в качестве контекста используется единственное ограничение с дополнительным шаблоном wildcard, например:
deriving instance _ => Eq (Foo a)
По сути, это то же самое, как если бы вы написали
deriving Eqпосле объявленияdata Foo a. Для использования этой возможности требуется расширениеPartialTypeSignatures(Частичные сигнатуры типов). -
В отличие от объявления
deriving, присоединённого к объявлениюdata, экземпляр может быть более конкретным, чем тип данных (при условии, что вы также используетеFlexibleInstances, Правила завершения для экземпляров). Например:data Foo a = Bar a | Baz String deriving instance Eq a => Eq (Foo [a]) deriving instance Eq a => Eq (Foo (Maybe a))
Это создаст выведенный экземпляр для
(Foo [a])и(Foo (Maybe a)), но другие типы, например(Foo (Int,Bool)), не будут экземплярамиEq. -
В отличие от объявления
deriving, присоединённого к объявлениюdata, GHC не ограничивает форму типа данных. Вместо этого GHC просто генерирует соответствующий шаблонный код для указанного класса и проверяет его тип. Если возникает ошибка типа, это ваша проблема. (Если в коде есть ошибка типа, GHC покажет проблемный фрагмент.)Преимущество этого подхода в том, что можно выводить экземпляры для GADT и других необычных типов данных, если только шаблонный код действительно проходит проверку типов. Например:
data T a where T1 :: T Int T2 :: T Bool deriving instance Show (T a)
В этом примере нельзя написать
... deriving( Show )в объявлении типа данных дляT, потому чтоT— это GADT, но можно сгенерировать объявление экземпляра с помощью отдельного вывода экземпляров.Недостаток в том, что, если шаблонный код не пройдёт проверку типов, вы получите сообщение об ошибке в этом коде, который вы не писали. При использовании предложения
derivingусловия применения неизбежно более консервативны, зато сообщения об ошибках могут быть понятнее. -
В большинстве случаев нельзя использовать отдельный вывод экземпляров для создания экземпляра типа данных, если не все его конструкторы находятся в области видимости. Это связано с тем, что выведенный экземпляр будет содержать код, использующий конструкторы скрытым образом, что нарушит абстракцию.
Единственное исключение из этого правила —
DeriveAnyClass, поскольку вывод экземпляра с помощьюDeriveAnyClassпросто создаёт пустое объявление экземпляра, для которого не требуется использовать конструкторы. Подробнее см. раздел вывод экземпляров любого класса.
В остальном отдельный вывод экземпляров подчиняется тем же правилам, что и обычный вывод экземпляров:
- Объявление
deriving instanceдолжно подчиняться тем же правилам формы и завершения, что и обычные объявления экземпляров; эти правила управляются теми же флагами. См. Объявления и разрешение экземпляров. -
Отдельный синтаксис обобщается для newtype точно так же, как обобщаются обычные предложения
deriving(Обобщённые выведенные экземпляры для newtype). Например:newtype Foo a = MkFoo (State Int a) deriving instance MonadState Int Foo
GHC всегда считает последний параметр экземпляра (
Fooв этом примере) типом, для которого выводится экземпляр.