:
>instance Applicative [] where
> pure x = [x]
> fs <*> xs = [f x | f <– fs, x <– xs]
Вспомните, что функция >pure
принимает значение и помещает его в контекст по умолчанию. Другими словами, она помещает его в минимальный контекст, который всё ещё возвращает это значение. Минимальным контекстом для списков был бы пустой список, но пустой список означает отсутствие значения, поэтому он не может содержать в себе значение, к которому мы применили функцию >pure
. Вот почему эта функция принимает значение и помещает его в одноэлементный список. Подобным образом минимальным контекстом для аппликативного функтора >Maybe
было бы значение >Nothing
– но оно означает отсутствие значения вместо самого значения, поэтому функция >pure
в реализации экземпляра для типа >Maybe
реализована как вызов конструктора данных >Just
.
Вот функция >pure
в действии:
>ghci> pure "Эй" :: [String]
>["Эй"]
>ghci> pure "Эй" :: Maybe String
>Just "Эй"
Что насчёт оператора ><*>
? Если бы тип оператора ><*>
ограничивался только списками, мы получили бы >(<*>) :: [a –> b] –> [a] –> [b]
. Этот оператор реализован через генератор списков. Он должен каким-то образом извлечь функцию из своего левого параметра, а затем с её помощью отобразить правый. Но левый список может не содержать в себе функций или содержать одну либо несколько функций, а правый список также может содержать несколько значений. Вот почему мы используем генератор списков для извлечения из обоих списков. Мы применяем каждую возможную функцию из левого списка к каждому возможному значению из правого. Результирующий список содержит все возможные комбинации применения функции из левого списка к значению из правого.
Мы можем использовать оператор ><*>
со списками вот так:
>ghci> [(*0),(+100),( 2)] <*> [1,2,3]
>[0,0,0,101,102,103,1,4,9]
Левый список содержит три функции, а правый – три значения, поэтому в результирующем списке будет девять элементов. Каждая функция из левого списка применяется к каждому элементу из правого. Если у нас имеется список функций, принимающих два параметра, то мы можем применить эти функции между двумя списками.
В следующем примере применяются две функции между двумя списками:
>ghci> [(+),(*)] <*> [1,2] <*> [3,4]
>[4,5,5,6,3,4,6,8]
Оператор ><*>
левоассоциативен, поэтому сначала выполняется >[(+),(*)] <*> [1,2]
, результатом чего является такой же список, как >[(1+),(2+),(1*),(2*)]
, потому что каждая функция слева применяется к каждому значению справа. Затем выполняется >[(1+),(2+),(1*),(2*)] <*> [3,4]
, что возвращает окончательный результат.
Как здорово использовать аппликативный стиль со списками!