Перевести с 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
  1. определение do Примечание:

    many1 space >> (many1 digit >>= \ds -> return $ read ds)
    
  2. определение $:

    many1 space >> (many1 digit >>= \ds -> return (read ds))
    
  3. определение .:

    many1 space >> (many1 digit >>= (return . read))
    
  4. 3-й закон монады (ассоциативность):

    (many1 space >> many1 digit) >>= (return . read)
    
  5. определение liftM (in неdo запись):

    liftM read (many1 space >> many1 digit)
    

это (или должно быть, если я не перепутал :)) идентично по поведению вашему примеру.

теперь, если вы замените liftM С fmap С <$> и >> С *>, вы получаете аппликативный:

read <$> (many1 space *> many1 digit)

это действительно, потому что liftM, fmap и <$> обычно считаются синонимами, как и >> и *>.

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