Производный экземпляр представляет собой объявление экземпляра, которое генерируется автоматически в связи с объявлением data или newtype. Тело объявления производного экземпляра получается синтаксически из определения связанного с ним типа. Производные экземпляры возможны только для классов, известных компилятору: тех, которые определены или в Prelude, или в стандартной библиотеке. В этой главе мы опишем выведение производных экземпляров классов, определенных в Prelude.
Если T --- алгебраический тип данных, объявленный с помощью:
data cx => T u1 ... uk | = | K1 t11 ... t1k1 | ...| Kn tn1 ... tnkn |
deriving (C1, ..., Cm) |
(где m>=0 и круглые скобки можно опустить, если m=1), тогда объявление производного экземпляра возможно для класса C, если выполняются следующие условия:
Если присутствует инструкция deriving, объявление экземпляра автоматически генерируется для T u1 ... uk по каждому классу Ci. Если объявление производного экземпляра невозможно для какого-либо из Ci, то возникнет статическая ошибка. Если производные экземпляры не требуются, инструкцию deriving можно опустить или можно использовать инструкцию deriving ().
Каждое объявление производного экземпляра будет иметь вид:
instance (cx, cx') => Ci (T u1 ... uk) where { d }
где d выводится автоматически в зависимости от Ci и объявления типа данных для T (как будет описано в оставшейся части этого раздела).
Контекст cx' является самым маленьким контекстом, удовлетворяющим приведенному выше пункту (2). Для взаимно рекурсивных типов данных компилятор может потребовать выполнения вычисления с фиксированной точкой, чтобы его вычислить.
Теперь дадим оставшиеся детали производных экземпляров для каждого из выводимых классов Prelude. Свободные переменные и конструкторы, используемые в этих трансляциях, всегда ссылаются на объекты, определенные в Prelude.
Выведенные сравнения всегда обходят конструкторы слева направо.
Приведенные ниже примеры иллюстрируют это свойство:
(1,undefined) == (2,undefined) => False
(undefined,1) == (undefined,2) => _|_
Все выведенные операции классов Eq и Ord являются строгими в отношении обоих аргументов.
Например, False <= _|_ является _|_, даже если False является первым конструктором
типа Bool .
Конструкторы с нулевым числом аргументов считаются пронумерованными слева направо индексами от 0 до n-1. Операторы succ и pred дают соответственно предыдущее и последующее значение в соответствии с этой схемой нумерации. Будет ошибкой применить succ к максимальному элементу или pred --- к минимальному элементу.
Операторы toEnum и fromEnum отображают перечислимые значения в значения типа Int и обратно; toEnum вызывает ошибку времени выполнения программы, если аргумент Int не является индексом одного из конструкторов.
Определения оставшихся методов:
enumFrom x = enumFromTo x lastCon
enumFromThen x y = enumFromThenTo x y bound
where
bound | fromEnum y >= fromEnum x = lastCon
| otherwise = firstCon
enumFromTo x y = map toEnum [fromEnum x .. fromEnum y]
enumFromThenTo x y z = map toEnum [fromEnum x, fromEnum y .. fromEnum z]
где firstCon и lastCon --- соответственно первый и последний
конструкторы, перечисленные в объявлении data.
Например,
с учетом типа данных:
data Color = Red | Orange | Yellow | Green deriving (Enum)
мы имели бы:
[Orange ..] == [Orange, Yellow, Green]
fromEnum Yellow == 2
Функция showsPrec d x r принимает в качестве аргументов уровень приоритета d (число от 0 до 11), значение x и строку r. Она возвращает строковое представление x, соединенное с r. showsPrec удовлетворяет правилу:
showsPrec d x r ++ s == showsPrec d x (r ++ s)
Представление будет заключено в круглые скобки, если приоритет конструктора верхнего уровня в x меньше чем d. Таким образом, если d равно 0, то результат никогда не будет заключен в круглые скобки; если d равно 11, то он всегда будет заключен в круглые скобки, если он не является атомарным выражением (вспомним, что применение функции имеет приоритет 10). Дополнительный параметр r необходим, если древовидные структуры должны быть напечатаны за линейное, а не квадратичное время от размера дерева.
Функция readsPrec d s принимает в качестве аргументов уровень приоритета d (число от 0 до 10) и строку s и пытается выполнить разбор значения в начале строки, возвращая список пар (разобранное значение, оставшаяся часть строки). Если нет успешного разбора, возвращаемый список пуст. Разбор инфиксного оператора, который не заключен в круглые скобки, завершится успешно, только если приоритет оператора больше чем или равен d.
Должно выполняться следующее:
showList и readList позволяют получить представление списков объектов, используя нестандартные обозначения. Это особенно полезно для строк (списков Char).
readsPrec выполняет разбор любого допустимого представления стандартных типов, кроме строк, для которых допустимы только строки, заключенные в кавычки, и других списков, для которых допустим только вид в квадратных скобках [...]. См. главу 8 для получения исчерпывающих подробностей.
Результат show представляет собой синтаксически правильное выражение Haskell , содержащее только константы, с учетом находящихся в силе infix-объявлений в месте, где объявлен тип. Он содержит только имена конструкторов, определенных в типе данных, круглые скобки и пробелы. Когда используются именованные поля конструктора, также используются фигурные скобки, запятые, имена полей и знаки равенства. Круглые скобки добавляются только там, где это необходимо, игнорируя ассоциативность. Никакие разрывы строк не добавляются. Результат show может быть прочитан с помощью read, если все типы компонент могут быть прочитаны. (Это выполняется для экземпляров, определенных в Prelude, но может не выполняться для определяемых пользователем экземпляров.)
Производные экземпляры класса Read делают следующие предположения, которым подчиняются производные экземпляры класса Show:
Производные экземпляры Read и Show могут не подходить для некоторых использований. Среди таких проблем есть следующие:
В качестве законченного примера рассмотрим тип данных дерево:
data Tree a = Leaf a | Tree a :^: Tree a
deriving (Eq, Ord, Read, Show)
Автоматическое выведение
объявлений
экземпляров для Bounded и Enum невозможны, поскольку Tree не является
перечислением и не является типом данных с одним конструктором. Полные
объявления экземпляров для Tree приведены на рис. 10.1.
Обратите внимание на неявное использование заданных по умолчанию определений методов классов
---
например, только <= определен для Ord, тогда как другие
методы класса (<, >, >=, max и min), определенные по умолчанию, заданы в
объявлении класса, приведенном на рис. 6.1
(стр. ).