Когда мы оборачиваем значение в аппликативное значение с помощью функции pure
, результат, который оно возвращает, должен быть этим значением. Минимальный контекст по умолчанию по-прежнему возвращает это значение в качестве результата. Вот почему в реализации экземпляра функция pure
принимает значение и создаёт функцию, которая игнорирует передаваемый ей параметр и всегда возвращает это значение. Тип функции pure
для экземпляра типа (–>) r
выглядит как pure :: a –> (r –> a)
.
ghci> (pure 3) "ля"
3
Из-за каррирования применение функции левоассоциативно, так что мы можем опустить скобки:
ghci> pure 3 "ля"
3
Реализация экземпляра <*>
немного загадочна, поэтому давайте посмотрим, как использовать функции в качестве аппликативных функторов в аппликативном стиле:
ghci> :t (+) <$> (+3) <*> (*100)
(+) <$> (+3) <*> (*100) :: (Num a) => a –> a
ghci> (+) <$> (+3) <*> (*100) $ 5
508
Вызов оператора <*>
с двумя аппликативными значениями возвращает аппликативное значение, поэтому если мы вызываем его с двумя функциями, то получаем функцию. Что же здесь происходит? Когда мы выполняем (+) <$> (+3) <*> (*100)
, мы создаём функцию, которая применит оператор + к результатам выполнения функций (+3)
и (*100)
и вернёт это значение. При вызове выражения (+) <$> (+3) <*> (*100) $ 5
функции (+3)
и (*100)
сначала применяются к значению 5, что в результате даёт 8 и 500; затем оператор +
вызывается со значениями 8 и 500, что в результате даёт 508.
Следующий код аналогичен:
ghci> (\x y z –> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5
[8.0,10.0,2.5]
Мы создаём функцию, которая вызовет функцию \x y z –> [x, y, z]
с окончательными результатами выполнения, возвращёнными функциями (+3)
, (*2)
и (/2)
. Значение 5
передаётся каждой из трёх функций, а затем с этими результатами вызывается анонимная функция \x y z –> [x, y, z]
.
ПРИМЕЧАНИЕ. Не так уж важно, поняли ли вы, как работает экземпляр типа (–>) r
для класса Applicative
, так что не отчаивайтесь, если вам это пока не ясно. Поработайте с аппликативным стилем и функциями, чтобы получить некоторое представление о том, как использовать функции в качестве аппликативных функторов.
Застёгиваемые списки
Оказывается, есть и другие способы для списков быть аппликативными функторами. Один способ мы уже рассмотрели: вызов оператора <*>
со списком функций и списком значений, который возвращает список всех возможных комбинаций применения функций из левого списка к значениям в списке справа.
Например, если мы выполним [(+3),(*2)] <*> [1,2]
, то функция (+3)
будет применена и к 1,
и к 2
; функция (*2)
также будет применена и к 1
, и к 2
, а результатом станет список из четырёх элементов: [4,5,2,4]
. Однако [(+3),(*2)] <*> [1,2]
могла бы работать и таким образом, чтобы первая функция в списке слева была применена к первому значению в списке справа, вторая была бы применена ко второму значению и т. д. Это вернуло бы список с двумя значениями: [4,4]
. Вы могли бы представить его как [1 + 3, 2 * 2]
.
Экземпляром класса Applicative
, с которым мы ещё не встречались, является тип ZipList
, и находится он в модуле Control.Applicative
.
Поскольку один тип не может иметь два экземпляра для одного и того же класса типов, был введён тип ZipList a
, в котором имеется один конструктор (ZipList
) с единственным полем (список). Вот так определяется его экземпляр:
instance Applicative ZipList where
pure x = ZipList (repeat x)
ZipList fs <*> ZipList xs = ZipList (zipWith (\f x –> f x) fs xs)
Оператор <*>
применяет первую функцию к первому значению, вторую функцию – ко второму значению, и т. д. Это делается с помощью выражения zipWith (\f x –> f x) fs xs
. Ввиду особенностей работы функции zipWith
окончательный список будет той же длины, что и более короткий список из двух.