-
DefaultSignatures -
- Начиная с:
-
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. Поэтому порядок переменных типов в сигнатуре по умолчанию важен. Вспомним пример
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.