Как вы используете Control.Applicative для написания более чистого Haskell?
В недавнем ответе на вопрос стиля я написал
main = untilM (isCorrect 42) (read `liftM` getLine)
и
isCorrect num guess =
case compare num guess of
EQ -> putStrLn "You Win!" >> return True
...
Martijn предлагаемые альтернативы:
main = untilM (isCorrect 42) (read <$> getLine)
EQ -> True <$ putStrLn "You Win!"
Какие общие шаблоны в коде Haskell можно сделать более ясными, используя абстракции из Control.Applicative? Какие полезные правила нужно иметь в виду для эффективного использования Control.Applicative?
Ответы
Ответ 1
В ответ на ваш вопрос многое можно сказать, так как вы спросили, я предложу это "эмпирическое правило".
Если вы используете do
-notation, а ваши сгенерированные значения [1] не используются в выражениях, которые вы упорядочиваете [2], тогда этот код может преобразовать в аппликативный стиль. Аналогичным образом, если вы используете одно или несколько сгенерированных значений в выраженном секвенсе, то вы должны использовать Monad
и Applicative
недостаточно для достижения того же кода.
Например, рассмотрим следующий код:
do a <- e1
b <- e2
c <- e3
return (f a b c)
Мы видим, что ни в одном из выражений справа от <-
не появляется какое-либо из сгенерированных значений (a
, b
, c
). Поэтому мы можем превратить его в использование аппликативного кода. Вот одно возможное преобразование:
f <$> e1 <*> e2 <*> e3
а другой:
liftA3 f e1 e2 e3
С другой стороны, возьмите этот фрагмент кода, например:
do a <- e1
b <- e2 a
c <- e3
return (f b c)
Этот код не может использовать Applicative
[3], потому что сгенерированное значение a
используется позже в выражении в понимании. Для получения результата нужно использовать Monad
- попытайтесь включить его в Applicative
, чтобы понять, почему.
Есть еще несколько интересных и полезных подробностей на эту тему, однако я просто намеревался дать вам это эмпирическое правило, с помощью которого вы можете обойтись в do
-понимание и довольно быстро определить, если его можно учесть в Applicative
код стиля.
[1] Те, которые отображаются слева от <-
.
[2] Выражения, которые отображаются справа от <-
.
[3], строго говоря, его части могли бы, разлагая e2 a
.
Ответ 2
В принципе, монады также являются аппликативными функторами [1]. Итак, всякий раз, когда вы используете liftM
, liftM2
и т.д., Вы можете объединить вычисления вместе с помощью <*>
. В некотором смысле вы можете придумать аппликативные функторы как аналогичные функциям. Чистую функцию f
можно снять, выполняя f <$> x <*> y <*> z
.
По сравнению с монадами, аппликативные функторы не могут выборочно выполнять свои аргументы. Побочные эффекты всех аргументов будут иметь место.
import Control.Applicative
ifte condition trueClause falseClause = do
c <- condition
if c then trueClause else falseClause
x = ifte (return True) (putStrLn "True") (putStrLn "False")
ifte' condition trueClause falseClause =
if condition then trueClause else falseClause
y = ifte' <$> (pure True) <*> (putStrLn "True") <*> (putStrLn "False")
x
выводит только True
, тогда как y
последовательно выводит True
и False
.
[1] The Typeclassopedia. Очень рекомендуется.
[2] http://www.soi.city.ac.uk/~ross/papers/Applicative.html. Хотя это академическая статья, ее не трудно следовать.
[3] http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors. Объясняет сделку очень хорошо.
[4] http://book.realworldhaskell.org/read/using-parsec.html#id652399. Показывает, как монадическая библиотека Parsec
также может быть использована аппликативным способом.
Ответ 3
См. Основы аппликативных функторов, поставленные на практическую работу Брайаном О'Салливаном.