Как здорово использовать аппликативный стиль со списками!
>ghci> (++) <$> ["хa","хeх","хм"] <*> ["?","!","."]
>["хa?","хa!","хa.","хeх?","хeх!","хeх.","хм?","хм!","хм."]
Ещё раз: мы использовали обычную функцию, принимающую две строки, между двумя списками строк, просто вставляя соответствующие аппликативные операторы.
Вы можете воспринимать списки как недетерминированные вычисления. Значение вроде >100
или >"что"
можно рассматривать как детерминированное вычисление, которое имеет только один результат. В то же время список вроде >[1,2,3]
можно рассматривать как вычисление, которое не в состоянии определиться, какой результат оно желает иметь, поэтому возвращает нам все возможные результаты. Поэтому когда вы пишете что-то наподобие >(+) <$> [1,2,3] <*> [4,5,6]
, то можете рассматривать это как объединение двух недетерминированных вычислений с помощью оператора >+
только для того, чтобы создать ещё одно недетерминированное вычисление, которое ещё меньше уверено в своём результате.
Использование аппликативного стиля со списками часто является хорошей заменой генераторам списков. В главе 1 мы хотели вывести все возможные комбинации произведений >[2,5,10]
и >[8,10,11]
и с этой целью предприняли следующее:
>ghci> [x*y | x <– [2,5,10], y <– [8,10,11]]
>[16,20,22,40,50,55,80,100,110]
Мы просто извлекаем значения из обоих списков и применяем функцию между каждой комбинацией элементов. То же самое можно сделать и в аппликативном стиле:
>ghci> (*) <$> [2,5,10] <*> [8,10,11]
>[16,20,22,40,50,55,80,100,110]
Для меня такой подход более понятен, поскольку проще понять, что мы просто вызываем оператор >*
между двумя недетерминированными вычислениями. Если бы мы захотели получить все возможные произведения элементов, больших 50, мы бы использовали следующее:
>ghci> filter (>50) $ (*) <$> [2,5,10] <*> [8,10,11]
>[55,80,100,110]
Легко увидеть, что вызов выражения >pure f <*> xs
при использовании списков эквивалентен выражению >fmap f xs
. Результат вычисления >pure f
– это просто >[f]
, а выражение >[f] <*> xs
применит каждую функцию в левом списке к каждому значению в правом; но в левом списке только одна функция, и, следовательно, это похоже на отображение.