Классы типов и роды

Все абстракции этого раздела (моноид, функтор, монада) оформлены в Haskell одним механизмом — классом типов: перегружаемый интерфейс и законы, которым обязаны подчиняться реализации. Класс с законами — это алгебраическая структура, а не просто интерфейс, как, скажем, в TypeScript.

Иерархия классов: Semigroup → Monoid; Functor → Applicative → Monad; Foldable и Functor → Traversable
Рис. 2.1. Классы раздела и их иерархия: стрелка ведёт от суперкласса к подклассу; Traversable требует и Functor, и Foldable.

В качестве примера рассмотрим класс типов Functor: объявление class задаёт интерфейс — сигнатуру единственного метода fmap\mathtt{fmap}, а объявление instance реализует этот интерфейс для конкретного типа — здесь для Maybe\mathtt{Maybe}.

class Functor f where
  -- fmap применяет обычную функцию внутри контекста f
  fmap :: (a -> b) -> f a -> f b
instance Functor Maybe where
  -- Если значение есть, применяем функцию к нему
  fmap g (Just x) = Just (g x)
  -- Если значения нет, контекст Nothing сохраняется
  fmap g Nothing = Nothing

Вызов метода класса диспетчеризуется по типу: компилятор видит, что fmap\mathtt{fmap} применяется к значению типа Maybe\mathtt{Maybe}, и подставляет экземпляр выше. Причём класс типов умеет диспетчеризоваться и по возвращаемому типу: например, mempty\mathtt{mempty} — нейтральный элемент из класса Monoid — аргументов не имеет вовсе и «узнаёт» свой тип из контекста вызова. Законы классов компилятор не проверяет — это контракт на совести автора экземпляра: класс можно инстанцировать неверно — скажем, написать fmap\mathtt{fmap}, который теряет элементы контейнера, — компилятор такой экземпляр примет, но код, полагающийся на законы, начнёт выдавать неверные результаты. И ещё одно правило: на один тип приходится не больше одного экземпляра класса (когерентность).

Теперь про род: он говорит, скольких параметров конструктору не хватает до обычного типа со значениями.

Int :: *
-- Int — обычный тип: у него есть значения

Maybe :: * -> *
-- Maybe — конструктор типов с одним параметром

Either :: * -> * -> *
-- Either — конструктор типов с двумя параметрами

Род * (он же Type\mathtt{Type}) — род обычных типов, у которых есть значения. Maybe\mathtt{Maybe} — не тип, а конструктор типов: функция на уровне типов, которая принимает тип (скажем, Int\mathtt{Int}) и возвращает тип (Maybe  Int\mathtt{Maybe\;Int}). Классы различаются родом параметра: классу Eq\mathtt{Eq} — сравнению на равенство — нужен обычный тип *, а классу Functor\mathtt{Functor} — конструктор * \to *: функтором бывает контейнер, но не значение. Частичное применение работает и на уровне типов: Either\mathtt{Either} имеет род * \to * \to *, но Either  e::\mathtt{Either\;e} \mathbin{\mathtt{::}} * \to * — уже законный функтор.