Вспомним, что классы типов по сути своей подобны интерфейсам. Они определяют некоторое поведение (проверку на равенство, проверку на «больше-меньше», перечисление элементов). Типы, обладающие таким поведением, можно сделать экземпляром класса типов. Поведение класса типов определяется функциями, входящими в класс, или просто декларацией класса; элементы класса мы потом должны будем реализовать. Таким образом, если мы говорим, что для типа имеется экземпляр класса, то подразумеваем, что можем использовать все функции, определённые в классе типов в нашем типе.
ПРИМЕЧАНИЕ. Классы типов практически не имеют ничего общего с классами в таких языках, как Java или Python. Это сбивает с толку, поэтому советую вам забыть всё, что вы знаете о классах в императивных языках!
Возьмём для примера класс типов >Eq
: он используется в отношении неких значений, которые можно проверить на равенство. Он определяет операторы >==
и >/=
. Если у нас есть тип, скажем, >Car
(автомобиль), и сравнение двух автомобилей с помощью функции >==
имеет смысл, то имеет смысл и определить для типа >Car
экземпляр класса >Eq
.
Вот как класс >Eq
определён в стандартном модуле:
>class Eq a where
> (==) :: a –> a –> Bool
> (/=) :: a –> a –> Bool
> x == y = not (x /= y)
> x /= y = not (x == y)
О-хо-хо!.. Новый синтаксис и новые ключевые слова. Не беспокойтесь, скоро мы это поясним. Прежде всего, мы записали декларацию >class Eq a where
– это означает, что мы определяем новый класс, имя которого >Eq
. Идентификатор >a
– это переменная типа; иными словами, идентификатор играет роль типа, который в дальнейшем будет экземпляром нашего класса. Эту переменную необязательно называть именно >a
; пусть даже имя не состоит из одной буквы, но оно непременно должно начинаться с символа в нижнем регистре. Затем мы определяем несколько функций. Нет необходимости писать реализацию функций – достаточно только декларации типа.
Некоторым будет проще понять эту декларацию, если мы запишем >class Eq equatable where
, а затем декларации функций, например >(==)
>::
>equatable
>–>
>equatable
>–>
>Bool
.
Мы определили тела функций для функций в классе >Eq
, притом определили их взаимно рекурсивно. Мы записали, что два экземпляра класса >Eq
равны, если они не отличаются, и что они отличаются, если не равны. Необязательно было поступать так, и всё же скоро мы увидим, чем это может быть полезно.
Если записать декларацию >class Eq a where
, описать в ней функцию таким образом: >(==) :: a -> a -> Bool
, а затем посмотреть объявление этой функции, мы увидим следующий тип: