instance Applicative Maybe where
pure = Just
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
Опять же из определения класса мы видим, что идентификатор f
, который играет роль аппликативного функтора, должен принимать один конкретный тип в качестве параметра. Поэтому мы пишем instance Applicative Maybe where
вместо instance Applicative (Maybe a) where
.
Далее, у нас есть функция pure
. Вспомните, что функция должна что-то принять и обернуть в аппликативное значение. Мы написали pure = Just
, потому что конструкторы данных вроде Just
являются обычными функциями. Также можно было бы написать pure x = Just x
.
Наконец, у нас есть определение оператора <*>
. Извлечь функцию из значения Nothing
нельзя, поскольку внутри него нет функции. Поэтому мы говорим, что если мы пробуем извлечь функцию из значения Nothing
, результатом будет то же самое значение Nothing
.
В определении класса Applicative
есть ограничение класса Functor
– значит, мы можем считать, что оба параметра оператора <*>
являются значениями функтора. Если первым аргументом выступает не значение Nothing
, а Just
с некоторой функцией внутри, то мы говорим, что с помощью данной функции хотим отобразить второй параметр. Этот код также заботится о случае, когда вторым аргументом является значение Nothing
, потому что его отображение с помощью любой функции при использовании метода fmap
вернёт всё то же Nothing
. Итак, в случае с типом Maybe
оператор <*>
извлекает функцию из значения слева, если это Just
, и отображает с её помощью значение справа. Если какой-либо из параметров является значением Nothing
, то и результатом будет Nothing
.
Теперь давайте это опробуем:
ghci> Just (+3) <*> Just 9
Just 12
ghci> pure (+3) <*> Just 10
Just 13
ghci> pure (+3) <*> Just 9
Just 12
ghci> Just (++"ха-ха") <*> Nothing Nothing
ghci> Nothing <*> Just "во-от"
Nothing
Вы видите, что выполнение выражений pure (+3)
и Just (+3)
в данном случае – одно и то же. Используйте функцию pure
, если имеете дело со значениями типа Maybe
в аппликативном контексте (если вы используете их с оператором <*>
); в противном случае предпочитайте конструктор Just
.
Первые четыре введённых строки демонстрируют, как функция извлекается, а затем используется для отображения; но в данном случае этого можно было добиться, просто применив не обёрнутые функции к функторам. Последняя строка любопытна тем, что мы пытаемся извлечь функцию из значения Nothing
, а затем отображаем с её помощью нечто, что в результате даёт Nothing
.
Когда вы отображаете функтор с помощью функции при использовании обычных функторов, вы не можете извлечь результат каким-либо общим способом, даже если результатом является частично применённая функция. Аппликативные функторы, с другой стороны, позволяют вам работать с несколькими функторами, используя одну функцию.
Аппликативный стиль
При использовании класса типов Applicative
мы можем последовательно задействовать несколько операторов <*>
в виде цепочки вызовов, что позволяет легко работать сразу с несколькими аппликативными значениями, а не только с одним. Взгляните, например, на это:
ghci> pure (+) <*> Just 3 <*> Just 5
Just 8
ghci> pure (+) <*> Just 3 <*> Nothing
Nothing
ghci> pure (+) <*> Nothing <*> Just 5
Nothing
Мы обернули оператор +
в аппликативное значение, а затем использовали оператор <*>
, чтобы вызвать его с двумя параметрами, оба из которых являются аппликативными значениями.
Давайте посмотрим, как это происходит, шаг за шагом. Оператор <*>
левоассоциативен; это значит, что
pure (+) <*> Just 3 <*> Just 5
то же самое, что и вот это:
(pure (+) <*> Just 3) <*> Just 5
Сначала оператор +
помещается в аппликативное значение – в данном случае значение типа Maybe
, которое содержит функцию. Итак, у нас есть pure (+)
, что, по сути, равно Just (+)
. Далее происходит вызов Just (+) <*> Just 3
. Его результатом является Just (3+)
. Это из-за частичного применения. Применение только значения 3
к оператору +
возвращает в результате функцию, которая принимает один параметр и добавляет к нему 3
. Наконец, выполняется Just (3+) <*> Just 5
, что в результате возвращает Just 8
.