Перевести с monad на applicative
хорошо, так что я знаю, что Applicative
тип класса содержит, и почему это полезно. Но я не могу полностью понять, как вы используете его в нетривиальном примере.
рассмотрим, например, следующий довольно простой парсер Parsec:
integer :: Parser Integer
integer = do
many1 space
ds <- many1 digit
return $ read ds
теперь, как, черт возьми, вы напишете это без использования Monad
экземпляр Parser
? Многие люди утверждают, что это можно сделать и это хорошая идея, но я не могу выяснить, как именно.
3 ответов
integer :: Parser Integer
integer = read <$> (many1 space *> many1 digit)
или
integer = const read <$> many1 space <*> many1 digit
считаете ли вы, что любой из них более читабельным до вас.
Я бы написал
integer :: Parser Integer
integer = read <$ many1 space <*> many1 digit
есть куча левых ассоциативных (например, application) операторов синтаксического анализа <$>
, <*>
, <$
, <*
. Вещь в крайнем левом углу должна быть чистой функцией, которая собирает значение результата из значений компонентов. Вещь справа от каждого оператора должна быть синтаксическим анализатором, коллективно дающим компоненты грамматики слева направо. Какой оператор использовать, зависит от двух вариантов: следует.
the thing to the right is signal / noise
_________________________
the thing to the left is \
+-------------------
pure / | <$> <$
a parser | <*> <*
Итак, выбрав read :: String -> Integer
как чистая функция, которая будет доставлять семантику синтаксического анализатора, мы можем классифицировать ведущее пространство как " шум "и кучу цифр как "сигнал", следовательно
read <$ many1 space <*> many1 digit
(..) (.........) (.........)
pure noise parser |
(.................) |
parser signal parser
(.................................)
parser
вы можете объединить несколько возможностей с
p1 <|> ... <|> pn
и невозможность выразить с
empty
редко необходимо называть компоненты в синтаксических анализаторах, и полученный код больше похож на грамматику с добавлена семантика.
ваш пример может быть постепенно переписан в форму, которая более четко напоминает аппликатор:
do
many1 space
ds <- many1 digit
return $ read ds
-
определение
do
Примечание:many1 space >> (many1 digit >>= \ds -> return $ read ds)
-
определение
$
:many1 space >> (many1 digit >>= \ds -> return (read ds))
-
определение
.
:many1 space >> (many1 digit >>= (return . read))
-
3-й закон монады (ассоциативность):
(many1 space >> many1 digit) >>= (return . read)
-
определение
liftM
(in неdo
запись):liftM read (many1 space >> many1 digit)
это (или должно быть, если я не перепутал :)) идентично по поведению вашему примеру.
теперь, если вы замените liftM
С fmap
С <$>
и >>
С *>
, вы получаете аппликативный:
read <$> (many1 space *> many1 digit)
это действительно, потому что liftM
, fmap
и <$>
обычно считаются синонимами, как и >>
и *>
.
это все работает и мы может сделать это, потому что исходный пример не использовал результат любого парсера для создания следующего парсера.