Ответ 1
integer :: Parser Integer
integer = read <$> (many1 space *> many1 digit)
или
integer = const read <$> many1 space <*> many1 digit
Думаете ли вы, что одно из них более читаемо, зависит от вас.
ОК, поэтому я знаю, что содержит класс типа Applicative
, и почему это полезно. Но я не могу полностью охватить мой мозг тем, как вы будете использовать его в нетривиальном примере.
Рассмотрим, например, следующий довольно простой парсер Parsec:
integer :: Parser Integer
integer = do
many1 space
ds <- many1 digit
return $ read ds
Теперь, как вы могли бы написать это без использования экземпляра Monad
для Parser
? Многие люди утверждают, что это можно сделать и это хорошая идея, но я не могу понять, как именно.
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
Там есть куча легальных ассоциативных операторов (например, приложений) для парсеров <$>
, <*>
, <$
, <*
. Вещью в крайнем левом углу должна быть чистая функция, которая собирает значение результата из значений компонента. Вещь справа от каждого оператора должна быть парсером, в совокупности предоставляющим компоненты грамматики слева направо. Какой оператор использовать, зависит от двух вариантов:
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
(в обозначениях не do
):
liftM read (many1 space >> many1 digit)
Это (или должно быть, если я не испортил:)), идентичный поведению вашего примера.
Теперь, если вы замените liftM
на fmap
на <$>
и >>
на *>
, вы получите Аппликацию:
read <$> (many1 space *> many1 digit)
Это справедливо, потому что liftM
, fmap
и <$>
обычно должны быть синонимами, такими как >>
и *>
.
Все это работает, и мы можем это сделать, потому что исходный пример не использовал результат любого парсера для создания следующего анализатора.