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