-
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в объявлении типа данных, контекст выводится автоматически.)Исключение из этого правила состоит в том, что контекст объявления вывода для самостоятельного использования может выводить свой контекст при использовании единственного дополнительного ограничения диких карт в качестве контекста, например:
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 покажет вам проблемный код, если в нём есть ошибка типа.)Достоинства этого подхода заключаются в том, что вы можете выводить экземпляры для GADTs и других экзотических типов данных, при условии, что шаблонный код действительно проходит проверку типов. Например:
data T a where T1 :: T Int T2 :: T Bool deriving instance Show (T a)
В этом примере вы не можете сказать
... deriving( Show )в объявлении типа данных дляT, потому чтоTявляется GADTem, но вы можете сгенерировать объявление экземпляра с помощью самостоятельного вывода.Недостатки заключаются в том, что если шаблонный код не пройдет проверку типов, вы получите сообщение об ошибке по этому коду, который вы не написали. В то время как с
derivingусловия ограничения неизбежно более консервативны, но любое сообщение об ошибке может быть более понятным. -
В большинстве случаев вы не можете использовать самостоятельный вывод для создания экземпляра типа данных, конструкторы которого не находятся в области видимости. Это связано с тем, что выведенный экземпляр сгенерирует код, который использует конструкторы за кулисами, что нарушит абстракцию.
Исключение из этого правила –
DeriveAnyClass, поскольку вывод экземпляра с помощьюDeriveAnyClassпросто генерирует пустое объявление экземпляра, которое не требует использования конструкторов. Более подробная информация представлена в разделе «Вывод для любых классов».
В других отношениях самостоятельный вывод подчиняется тем же правилам, что и обычный вывод:
- Объявление
deriving instanceдолжно подчиняться тем же правилам относительно формы и завершения, что и обычные объявления экземпляров, управляемые теми же флагами; см. Объявления и разрешение экземпляров. -
Синтаксис для самостоятельного использования обобщен для новых типов точно так же, как обобщаются обычные
deriving( Обобщенные выведенные экземпляры для новых типов). Например:newtype Foo a = MkFoo (State Int a) deriving instance MonadState Int Foo
GHC всегда обрабатывает последний параметр экземпляра (
Fooв этом примере) как тип, для которого выводится экземпляр.