Все абстракции этого раздела (моноид, функтор, монада) оформлены в Haskell одним механизмом — классом типов: перегружаемый интерфейс и законы, которым обязаны подчиняться реализации. Класс с законами — это алгебраическая структура, а не просто интерфейс, как, скажем, в TypeScript.
В качестве примера рассмотрим класс типов Functor: объявление class задаёт интерфейс — сигнатуру единственного метода , а объявление instance реализует этот интерфейс для конкретного типа — здесь для .
class Functor f where
-- fmap применяет обычную функцию внутри контекста f
fmap :: (a -> b) -> f a -> f binstance Functor Maybe where
-- Если значение есть, применяем функцию к нему
fmap g (Just x) = Just (g x)
-- Если значения нет, контекст Nothing сохраняется
fmap g Nothing = NothingВызов метода класса диспетчеризуется по типу: компилятор видит, что применяется к значению типа , и подставляет экземпляр выше. Причём класс типов умеет диспетчеризоваться и по возвращаемому типу: например, — нейтральный элемент из класса Monoid — аргументов не имеет вовсе и «узнаёт» свой тип из контекста вызова. Законы классов компилятор не проверяет — это контракт на совести автора экземпляра: класс можно инстанцировать неверно — скажем, написать , который теряет элементы контейнера, — компилятор такой экземпляр примет, но код, полагающийся на законы, начнёт выдавать неверные результаты. И ещё одно правило: на один тип приходится не больше одного экземпляра класса (когерентность).
Теперь про род: он говорит, скольких параметров конструктору не хватает до обычного типа со значениями.
Int :: *
-- Int — обычный тип: у него есть значения
Maybe :: * -> *
-- Maybe — конструктор типов с одним параметром
Either :: * -> * -> *
-- Either — конструктор типов с двумя параметрамиРод (он же ) — род обычных типов, у которых есть значения. — не тип, а конструктор типов: функция на уровне типов, которая принимает тип (скажем, ) и возвращает тип (). Классы различаются родом параметра: классу — сравнению на равенство — нужен обычный тип , а классу — конструктор : функтором бывает контейнер, но не значение. Частичное применение работает и на уровне типов: имеет род , но — уже законный функтор.