Функция pure
здесь также интересна. Она берёт значение и помещает его в список, в котором это значение просто повторяется бесконечно. Выражение pure "ха-ха"
вернёт ZipList (["ха-ха","ха-ха","ха-ха"…
Это могло бы сбить с толку, поскольку вы узнали, что функция pure
должна помещать значение в минимальный контекст, который по-прежнему возвращает данное значение. И вы могли бы подумать, что бесконечный список чего-либо едва ли является минимальным. Но это имеет смысл при использовании застёгиваемых списков, так как значение должно производиться в каждой позиции. Это также удовлетворяет закону о том, что выражение pure f <*> xs
должно быть эквивалентно выражению fmap f xs
. Если бы вызов выражения pure 3
просто вернул ZipList [3]
, вызов pure (*2) <*> ZipList [1,5,10]
дал бы в результате ZipList [2]
, потому что длина результирующего списка из двух застёгнутых списков равна длине более короткого списка из двух. Если мы застегнём конечный список с бесконечным, длина результирующего списка всегда будет равна длине конечного списка.
Так как же застёгиваемые списки работают в аппликативном стиле? Давайте посмотрим.
Ладно, тип ZipList
a не имеет экземпляра класса Show
, поэтому мы должны использовать функцию getZipList
для извлечения обычного списка из застёгиваемого:
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100,100]
[101,102,103]
ghci> getZipList $ (+) <$> ZipList [1,2,3] <*> ZipList [100,100..]
[101,102,103]
ghci> getZipList $ max <$> ZipList [1,2,3,4,5,3] <*> ZipList [5,3,1,2]
[5,3,3,4]
ghci> getZipList $ (,,) <$> ZipList "пар" <*> ZipList "ток" <*> ZipList "вид"
[('п','т','в'),('а','о','и'),('р',кt','д')]
ПРИМЕЧАНИЕ. Функция (,,)
– это то же самое, что и анонимная функция \x y z –> (x,y,z)
. В свою очередь, функция (,)
– то же самое, что и \x y –> (x,y)
.
Помимо функции zipWith
в стандартной библиотеке есть такие функции, как zipWith3
, zipWith4
, вплоть до 7
. Функция zipWith
берёт функцию, которая принимает два параметра, и застёгивает с её помощью два списка. Функция zipWith3
берёт функцию, которая принимает три параметра, и застёгивает с её помощью три списка, и т. д. При использовании застёгиваемых списков в аппликативном стиле нам не нужно иметь отдельную функцию застёгивания для каждого числа списков, которые мы хотим застегнуть друг с другом. Мы просто используем аппликативный стиль для застёгивания произвольного количества списков при помощи функции, и это очень удобно.
Аппликативные законы
Как и в отношении обычных функторов, применительно к аппликативным функторам действует несколько законов. Самый главный состоит в том, чтобы выполнялось тождество pure f <*> x = fmap f x
. В качестве упражнения можете доказать выполнение этого закона для некоторых аппликативных функторов из этой главы. Ниже перечислены другие аппликативные законы:
• pure id
<*>
v
=
v
• pure
(.)
<*>
u
<*>
v
<*>
w
=
u
<*>
(v
<*>
w)
• pure
f
<*>
pure
x
=
pure
(f
x
)
• u
<*>
pure
y
=
pure
($
y)
<*>
u
Мы не будем рассматривать их подробно, потому что это заняло бы много страниц и было бы несколько скучно. Если вам интересно, вы можете познакомиться с этими законами поближе и посмотреть, выполняются ли они для некоторых экземпляров.
Полезные функции для работы с аппликативными функторами
Модуль Control.Applicative
определяет функцию, которая называется liftA2
и имеет следующий тип:
liftA2 :: (Applicative f) => (a –> b –> c) –> f a –> f b –> f c
Она определена вот так:
liftA2 :: (Applicative f) => (a –> b –> c) –> f a –> f b –> f c
liftA2 f a b = f <$> a <*> b
Она просто применяет функцию между двумя аппликативными значениями, скрывая при этом аппликативный стиль, который мы обсуждали. Однако она ясно демонстрирует, почему аппликативные функторы более мощны по сравнению с обычными.