-
NamedDefaults -
- Начиная с:
-
9.12.1
Расширение NamedDefaults расширяет механизм задания типов по умолчанию для классов типов, описанный в Задание типов по умолчанию для классов типов, позволяя указывать типы по умолчанию отдельно для каждого класса, а также импортировать и экспортировать их.
6.11.3.1. Мотивация
В языковом отчёте Haskell 2010 есть редко используемые объявления default. Их основное назначение — упростить работу с числовыми литералами в небольших программах:
main = print (6 * 7)
Тип правой части этого уравнения — (Num a, Show a) => IO
(). Ничто не указывает компилятору, какой конкретный тип следует использовать для a. Чтобы устранить эту неоднозначность, пользователь может объявить, например:
default (Integer, Int, Double)
чтобы указать, что любая неоднозначная переменная типа (например, a в приведённом выше примере), ограниченная классом Num, должна получать по умолчанию тип Integer. Если этот тип не удовлетворяет остальным ограничениям контекста (например, Show a выше), последовательно проверяются другие указанные типы (например, Int и Double).
Языковой отчёт применяет объявления default только к неоднозначностям, связанным с классом Num. Это ограничение объясняется тем, что в языковом отчёте неоднозначные типы есть только у числовых литералов. Однако с тех пор языковые расширения OverloadedStrings и OverloadedLists добавили неоднозначность для большего числа синтаксических конструкций. Первое расширение, в частности, часто используется для более удобного написания литералов Text. Ещё одним потенциальным источником неоднозначности являются Prelude и основные библиотеки, которые постепенно становятся более обобщёнными.
6.11.3.2. Указание класса
В языковом отчёте Haskell 2010 для объявления значений по умолчанию задан следующий синтаксис:
где каждый тип qtyconi должен быть экземпляром класса Num.
С языковым расширением NamedDefaults GHC обобщает эти объявления, позволяя пользователю указывать типы по умолчанию для любого класса, а не только для Num:
где каждый тип qtyconi должен быть экземпляром указанного класса qtycls. Типы могут иметь любой род, но класс должен иметь ровно один видимый параметр. Например, теперь допустимо следующее объявление:
default Monoid ([Int])
что, в свою очередь, позволяет скомпилировать эту программу:
main = print mempty
Обратите внимание, что у класса могут быть невидимые параметры типа; например, следующий вариант допустим:
-- recall Typeable :: forall k. k -> Constraint
default Typeable (Int)
type RepPolyCls :: forall r. TYPE r -> Constraint
class RepPolyCls a where { ... }
default RepPolyCls (Int#)
Здесь Typeable и RepPolyCls принимают по два параметра: невидимый параметр рода и видимый параметр типа. Для именованных объявлений значений по умолчанию они считаются унарными.
Если класс не указан, предполагается ранее использовавшееся значение по умолчанию Num. Иными словами, синтаксис Haskell ‘98:
default (Int, Float)
будет означать то же самое, что и:
default Num (Int, Float)
6.11.3.3. Экспорт значений по умолчанию
Объявление default само по себе действует только внутри модуля, в котором оно находится. Расширение NamedDefaults также дополняет синтаксис экспорта модулей, добавляя новую форму элемента экспорта:
module MyModule (default Monoid)
которая экспортирует значение по умолчанию, действующее в модуле для указанного класса. Это значение может быть объявлено в том же модуле или импортировано из другого модуля.
При экспорте объявления default Num класс Num необходимо явно указать, как и любой другой класс.
Модуль без явного списка экспорта (как в module M where {...}) экспортирует все объявленные в нём значения по умолчанию. Повторный экспорт целого импортированного модуля (как в module M (module N) where{...}) не экспортирует никакие значения по умолчанию.
Хотя экспорт значений по умолчанию должен быть явным, их импорт происходит автоматически и неявно. Чтобы подавить или изменить импортированное значение по умолчанию, модуль может объявить собственное: локальное объявление default переопределяет все импортированные значения по умолчанию для того же класса.
6.11.3.4. Определение поглощения
Пусть заданы два объявления default для одного класса
Если m ≤ n, а первая последовательность типов Type1a , … , Typema является подпоследовательностью второй последовательности Type1b , … , Typenb (то есть, первую можно получить из второй, удалив некоторое количество элементов Typeib), то говорят, что второе объявление поглощает первое.
6.11.3.5. Правила разрешения неоднозначностей при наличии нескольких объявлений
В одном модуле для любого конкретного класса может действовать только одно объявление default. Если в области видимости находится несколько объявлений default, конфликт разрешается по следующим правилам:
- Объявления для двух разных классов не считаются конфликтующими, однако в конкретном месте использования они могут столкнуться, как будет показано в следующем разделе.
- Два объявления для одного и того же класса, явно объявленные в одном модуле, считаются статической ошибкой.
- Объявление
defaultв модуле имеет приоритет над любыми импортированными объявлениямиdefaultдля того же класса. Однако компилятор может выдать предупреждение (включаемое с помощью-Wtype-defaults), если импортированное объявление не поглощается локальным. - Для любых двух импортированных объявлений
defaultодного класса, если одно поглощает другое, поглощённое объявление игнорируется. - Если для класса нет ни локального объявления
default, ни импортированного объявленияdefault, поглощающего все остальные импортированные объявленияdefaultэтого класса, конфликт между импортами неразрешим. В результате все объявленияdefaultэтого класса игнорируются, и в модуле не действует ни одно из них. В этом случае компилятор может выдать предупреждение, если оно включено с помощью-Wtype-defaults, но ошибки из-за импортов не возникнет. Разумеется, в теле модуля может возникнуть ошибка, если оно содержит фактический неоднозначный тип для класса с конфликтующими импортированными значениями по умолчанию, как описано в следующем подразделе.
Таким образом, в каждом модуле для каждого класса действует либо одно объявление значения по умолчанию (локальное или импортированное, поглощающее все остальные импортированные объявления), либо ни одного. Это единственное значение по умолчанию используется для разрешения неоднозначностей, как описано в следующем подразделе.
Обратите внимание: объявление default, в котором имя типа повторяется несколько раз, вполне допустимо и иногда может понадобиться для разрешения конфликтов. Например, модуль, импортирующий два конфликтующих значения по умолчанию,
default C (Int, Bool)
и
default C (Bool, Int)
может использовать локальное объявление
default C (Int, Bool, Int)
для переопределения импортов. Поскольку это объявление поглощает оба импортированных значения по умолчанию, предупреждение компилятора не появится. При разрешении неоднозначности (в следующем разделе) оно ведёт себя точно так же, как default C( Int, Bool); то есть повторы можно отбросить.
6.11.3.6. Правила разрешения неоднозначностей в месте использования
Правила разрешения неоднозначностей являются консервативным расширением существующих правил из (Отчёта Haskell, раздел 4.3.4). Они описаны в разделе Задание типов по умолчанию для классов типов, но кратко напомним: неоднозначная переменная типа v может получить значение по умолчанию, если:
- v встречается только в ограничениях вида C v, где C — класс, и
- как минимум один из этих классов является числовым (то есть
Numили подклассомNum), и - все эти классы определены в Prelude или стандартной библиотеке.
Каждая переменная, которой можно назначить значение по умолчанию, заменяется первым типом из списка значений по умолчанию, являющимся экземпляром всех классов, которым удовлетворяет эта неоднозначная переменная. Если такой тип не найден, возникает статическая ошибка.
Новые правила смягчают последние два условия, распространяя их на любые классы, для которых есть именованное объявление значения по умолчанию (локальное или импортированное). Обратите внимание, что это относится и к модулям, в которых расширение NamedDefaults не включено!
Упрощённо говоря, тип, выбираемый по умолчанию, — это первый тип из списка default для класса C, удовлетворяющий всем ограничениям для переменной типа v. Если есть несколько ограничений Ci v с конфликтующими объявлениями default, они должны приводить к одному и тому же типу.
Чтобы точнее описать этот механизм, для выбора значения по умолчанию можно использовать следующий алгоритм, однако допустим и любой другой метод, дающий тот же результат:
Пусть S — полное множество нерешённых ограничений, а Sx изначально является пустым множеством ограничений. Для каждой переменной v, свободной в S:
- Определим Cv = { Ci v | Ci v ∈ S } — подмножество S, состоящее из всех ограничений вида (Ci v), где Ci — класс типов с одним параметром.
- Определим Dv, дополнив Cv суперклассами каждого Ci из Cv
- Определим Ev, отфильтровав Dv так, чтобы в нём остались только классы с объявлением значения по умолчанию.
- Для каждого Ci из Ev найдём первый тип T в списке значений по умолчанию для Ci, для которого ограничение (Ci T) разрешимо для каждого (Ci v) из Cv.
- Если в полученном множестве типов ровно один тип T, разрешим неоднозначность, добавив ограничение
v ~ Ti в множество Sx; в противном случае сообщим о статической ошибке.
Как объясняется в разделе Расширения механизма задания типов по умолчанию для классов типов, расширение ExtendedDefaultRules позволяет ещё сильнее смягчить эти правила и применять задание типов по умолчанию в большем числе случаев.