Изучай Haskell во имя добра! (Липовача) - страница 102

Вспомним, что классы типов по сути своей подобны интерфейсам. Они определяют некоторое поведение (проверку на равенство, проверку на «больше-меньше», перечисление элементов). Типы, обладающие таким поведением, можно сделать экземпляром класса типов. Поведение класса типов определяется функциями, входящими в класс, или просто декларацией класса; элементы класса мы потом должны будем реализовать. Таким образом, если мы говорим, что для типа имеется экземпляр класса, то подразумеваем, что можем использовать все функции, определённые в классе типов в нашем типе.

ПРИМЕЧАНИЕ. Классы типов практически не имеют ничего общего с классами в таких языках, как Java или Python. Это сбивает с толку, поэтому советую вам забыть всё, что вы знаете о классах в императивных языках!

«Внутренности» класса Eq

Возьмём для примера класс типов >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, а затем посмотреть объявление этой функции, мы увидим следующий тип: