Если мы отображаем список символов с помощью функции compare, которая имеет тип (Ord a) => a –> a –> Ordering, то получаем список функций типа Char –> Ordering, потому что функция compare частично применяется с помощью символов в списке. Это не список функций типа (Ord a) => a –> Ordering, так как первый идентификатор переменной типа a имел тип Char, а потому и второе вхождение a обязано принять то же самое значение – тип Char.

Мы видим, как, отображая значения функторов с помощью «многопараметрических» функций, мы получаем значения функторов, которые содержат внутри себя функции. А что теперь с ними делать?.. Мы можем, например, отображать их с помощью функций, которые принимают эти функции в качестве параметров – поскольку, что бы ни находилось в значении функтора, оно будет передано функции, с помощью которой мы его отображаем, в качестве параметра.

ghci> let a = fmap (*) [1,2,3,4]

ghci> :t a

a :: [Integer –> Integer]

ghci> fmap (\f –> f 9) a

[9,18,27,36]

Но что если у нас есть значение функтора Just (3 *) и значение функтора Just 5, и мы хотим извлечь функцию из Just (3 *) и отобразить с её помощью Just 5? С обычными функторами у нас этого не получится, потому что они поддерживают только отображение имеющихся функторов с помощью обычных функций. Даже когда мы отображали функтор, содержащий функции, с помощью анонимной функции \f –> f 9, мы делали именно это и только это. Но используя то, что предлагает нам функция fmap, мы не можем с помощью функции, которая находится внутри значения функтора, отобразить другое значение функтора. Мы могли бы произвести сопоставление конструктора Just по образцу для извлечения из него функции, а затем отобразить с её помощью Just 5, но мы ищем более общий и абстрактный подход, работающий с функторами.

<p>Поприветствуйте аппликативные функторы</p>

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

<p>Аппликативный функтор Maybe</p>

Давайте взглянем на реализацию экземпляра класса Applicative для типа Maybe:

Перейти на страницу:

Похожие книги