Перевести с монады на прикладную

ОК, поэтому я знаю, что содержит класс типа Applicative, и почему это полезно. Но я не могу полностью охватить мой мозг тем, как вы будете использовать его в нетривиальном примере.

Рассмотрим, например, следующий довольно простой парсер Parsec:

integer :: Parser Integer
integer = do
  many1 space
  ds <- many1 digit
  return $ read ds

Теперь, как вы могли бы написать это без использования экземпляра Monad для Parser? Многие люди утверждают, что это можно сделать и это хорошая идея, но я не могу понять, как именно.

Ответ 1

integer :: Parser Integer
integer = read <$> (many1 space *> many1 digit)

или

integer = const read <$> many1 space <*> many1 digit

Думаете ли вы, что одно из них более читаемо, зависит от вас.

Ответ 2

Я бы написал

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

Редко необходимо назвать компоненты в синтаксических анализаторах, а полученный код больше похож на грамматику с добавленной семантикой.

Ответ 3

Ваш пример может быть полностью переписан в форму, которая более четко напоминает аппликатор:

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 и <$> обычно должны быть синонимами, такими как >> и *>.

Все это работает, и мы можем это сделать, потому что исходный пример не использовал результат любого парсера для создания следующего анализатора.