Поприветствуйте аппликативные функторы
Итак, встречайте класс типов Applicative, находящийся в модуле Control.Applicative!.. Он определяет две функции: pure и <*>. Он не предоставляет реализации по умолчанию для какой-либо из этих функций, поэтому нам придётся определить их обе, если мы хотим, чтобы что-либо стало аппликативным функтором. Этот класс определён вот так:
class (Functor f) => Applicative f where
pure :: a –> f a
(<*>) :: f (a –> b) –> f a –> f b
Простое определение класса из трёх строк говорит нам о многом!.. Первая строка начинается с определения класса Applicative; также она вводит ограничение класса. Ограничение говорит, что если мы хотим определить для типа экземпляр класса Applicative, он, прежде всего, уже должен иметь экземпляр класса Functor. Вот почему, когда нам известно, что конструктор типа принадлежит классу Applicative, можно смело утверждать, что он также принадлежит классу Functor, так что мы можем применять к нему функцию fmap.
Первый метод, который он определяет, называется pure. Его сигнатура выглядит так: pure :: a –> f a. Идентификатор f играет здесь роль нашего экземпляра аппликативного функтора. Поскольку язык Haskell обладает очень хорошей системой типов и притом всё, что может делать функция, – это получать некоторые параметры и возвращать некоторое значение, мы можем многое сказать по объявлению типа, и данный тип – не исключение.
Функция pure должна принимать значение любого типа и возвращать аппликативное значение с этим значением внутри него. Словосочетание «внутри него» опять вызывает в памяти нашу аналогию с коробкой, хотя мы и видели, что она не всегда выдерживает проверку. Но тип a –> f a всё равно довольно нагляден. Мы берём значение и оборачиваем его в аппликативное значение, которое содержит в себе это значение в качестве результата. Лучший способ представить себе функцию pure – это сказать, что она берёт значение и помещает его в некий контекст по умолчанию (или чистый контекст) – минимальный контекст, который по-прежнему возвращает это значение.
Оператор <*> действительно интересен. У него вот такое определение типа:
f (a –> b) –> f a –> f b
Напоминает ли оно вам что-нибудь? Оно похоже на сигнатуру fmap :: (a –> b) –> f a –> f b. Вы можете воспринимать оператор <*> как разновидность расширенной функции fmap. Тогда как функция fmap принимает функцию и значение функтора и применяет функцию внутри значения функтора, оператор <*> принимает значение функтора, который содержит в себе функцию, и другой функтор – и извлекает эту функцию из первого функтора, затем отображая с её помощью второй.