-
DefaultSignatures -
- Since:
-
7.2.1
Позволяет определять подписи методов по умолчанию в определениях классов.
Haskell 98 позволяет определять реализацию по умолчанию при объявлении класса:
class Enum a where enum :: [a] enum = []
Тип метода enum равен [a], и это также тип метода по умолчанию. Вы можете изменить тип метода по умолчанию, потребовав другой контекст, используя расширение DefaultSignatures. Например, если вы написали обобщённую реализацию перечисления в классе GEnum с методом genum, вы можете указать метод по умолчанию, использующий эту обобщённую реализацию. Но ваша реализация по умолчанию может быть использована только если ограничения удовлетворены, поэтому вам нужно изменить тип метода по умолчанию
class Enum a where enum :: [a] default enum :: (Generic a, GEnum (Rep a)) => [a] enum = map to genum
Мы повторно используем ключевое слово default для обозначения того, что подпись относится только к методу по умолчанию; при определении экземпляров класса Enum, исходный тип [a] метода enum всё ещё применим. Однако, при предоставлении пустого экземпляра реализация по умолчанию (map to genum) заполняется и проверяется с типом (Generic a, GEnum (Rep a)) => [a].
Подпись типа для метода по умолчанию класса должна иметь ту же форму, что и подпись типа соответствующего основного метода. В противном случае компилятор типов отклонит определение класса. Под «той же формой» мы подразумеваем, что подпись типа по умолчанию должна отличаться от основной подписи типа только в их внешних контекстах. Следовательно, если у вас есть метод bar:
class Foo a where bar :: forall b. C => a -> b -> b
Тогда метод по умолчанию для bar должен иметь форму:
default bar :: forall b. C' => a -> b -> b bar = ...
C может отличаться от C', но правые части подписей типа должны совпадать. Мы требуем этого, потому что при объявлении пустого экземпляра для класса, использующего DefaultSignatures, GHC неявно заполняет реализацию по умолчанию таким образом:
instance Foo Int where bar = default_bar
Где default_bar — это функция верхнего уровня, основанная на подписи и реализации типа по умолчанию для bar:
default_bar :: forall a b. (Foo a, C') => a -> b -> b default_bar = ...
Для работы этого подхода подпись типа по умолчанию для bar должна быть такой же, как и подпись без по умолчанию, за исключением внешнего контекста (с некоторыми оговорками — см. Подробные требования к подписям типов по умолчанию). Нет обязательства для C и C' быть одинаковыми, и, на самом деле, пример Enum выше полагается на то, что подпись типа по умолчанию для enum имеет более конкретный контекст, чем исходная подпись типа.
Мы используем подписи по умолчанию для упрощения обобщённого программирования в GHC (Обобщённое программирование).
6.8.5. Подробные требования к подписям типов по умолчанию
Остальная часть этого раздела даёт более подробную информацию о том, что представляет собой допустимая подпись типа по умолчанию.
-
Игнорируя внешние контексты, подпись типа по умолчанию должна соответствовать исходной подписи типа согласно правилам субсупции GHC (Правилам субсупции GHC). В результате порядок типов переменных в подписи по умолчанию важен. Вспомните пример
Fooиз предыдущего раздела:class Foo a where bar :: forall b. C => a -> b -> b default bar :: forall b. C' => a -> b -> b bar = ...
Это законно, потому что если удалить внешние контексты
CиC', то две подписи типа будут одинаковыми. Однако, это не обязательно означает, что подпись типа по умолчанию должна быть точно такой же. Например, это также будет приемлемая подпись типа по умолчанию, поскольку она альфа-эквивалентна исходной подписи типа:default bar :: forall x. C' => a -> x -> x
С другой стороны, это не приемлемая подпись типа по умолчанию, так как переменная типа
aнаходится в неправильном месте:default bar :: forall b. C' => b -> a -> b
-
Единственное место, где подпись типа по умолчанию может быть более точной, чем исходная подпись типа, — это внешний контекст. Например, это не будет приемлемая подпись типа по умолчанию, так как мы не можем сопоставить переменную типа
bс конкретным типомInt:default bar :: C' => a -> Int -> Int
Однако, вы можете использовать равенства типов для достижения того же результата:
default bar :: forall b. (C', b ~ Int) => a -> b -> b
-
Из-за правил субсупции GHC существуют относительно строгие ограничения в отношении вложенных или высокоранговых
forall(см. Произвольная полиморфия ранга). Рассмотрим этот класс:class C x where m :: x -> forall a b. a -> b
GHC не позволит следующую подпись типа по умолчанию для
m:default m :: x -> forall b a. a -> b
Это происходит потому, что подпись по умолчанию квантует вложенные
forallв другом порядке, чем исходная подпись типа. Для того, чтобы это проверилось, подпись по умолчанию должна сохранить исходный порядок:default m :: x -> forall a b. a -> b
Обратите внимание, что в отличие от вложенных или высокоранговых
forallвнешниеforallимеют большую гибкость в том, как они упорядочиваются. В результате GHC позволит следующее:class C' x where m' :: forall a b. x -> a -> b default m' :: forall b a. x -> a -> b m' = ...
-
Так же, как порядок вложенных или высокоранговых
forallограничен, аналогичное ограничение применяется к порядку появления вложенных или высокоранговых контекстов. В результате GHC не позволит следующее:class D a where n :: a -> forall b. (Eq b, Show b) => b -> String default n :: a -> forall b. (Show b, Eq b) => b -> String n = ...
Однако, GHC позволит переупорядочить ограничения в рамках внешнего контекста, как показано тем, что GHC принимает следующее:
class D' a where n' :: (Eq b, Show b) => a -> b -> String default n' :: (Show b, Eq b) => a -> b -> String n' = ...
-
Поскольку подпись по умолчанию разрешается отличаться от исходной подписи типа только во внешнем контексте, а не в вложенных или высокоранговых контекстах, существуют некоторые значения по умолчанию, которые нельзя записать без переупорядочения
forall. Рассмотрим этот пример:class E a where p :: Int -> forall b. b -> String
Предположим, что кто-то хочет написать подпись по умолчанию для
p, где контекст должен упоминать какa, так иb. Хотя естественно было бы написать эту подпись по умолчанию:default p :: Int -> forall b. DefaultClass a b => b -> String
Это не пройдёт проверку типов, так как подпись типа по умолчанию теперь отличается от исходной подписи типа в использовании вложенных контекстов. Единственный способ сделать такую подпись по умолчанию работоспособной — это изменить порядок, в котором
bквантуется:default p :: forall b. DefaultClass a b => Int -> b -> String
Это работает, но ценой изменения поведения
pотносительно Видимого применения типа. -
Подпись по умолчанию не может использовать какие-либо неоднозначные переменные типа. Например, GHC не позволит экземпляры следующего класса:
class A t where f :: forall x m. Monoid x => t m -> m default :: forall x m. Monoid x => t m -> m f = ...
Обратите внимание, что
xнеоднозначна, поскольку ни один из аргументов или типов результатов не определяет, каким типомxдолжен быть при вызовеf. GHC отклонит экземплярыA, даже еслиAllowAmbiguousTypesвключён.