Изучай Haskell во имя добра! (Липовача) - страница 188

:

>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], что возвращает окончательный результат.

Как здорово использовать аппликативный стиль со списками!