Итак, встречайте класс типов >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
принимает функцию и значение функтора и применяет функцию внутри значения функтора, оператор ><*>
принимает значение функтора, который содержит в себе функцию, и другой функтор – и извлекает эту функцию из первого функтора, затем отображая с её помощью второй.