如何在不假设Monad的情况下为解析器实现Applicative实例?

时间:2012-10-31 20:07:07

标签: parsing haskell applicative

我无法弄清楚如何为此解析器实现Applicative实例:

newtype Parser m s a = Parser { getParser :: [s] -> m ([s], a) }

不假设Monad m。我希望只需要假设Applicative m,因为Functor实例只需要假设Functor m。我终于结束了:

instance Functor m => Functor (Parser m s) where
  fmap f (Parser g) = Parser (fmap (fmap f) . g)


instance Monad m => Applicative (Parser m s) where
  pure a = Parser (\xs -> pure (xs, a))

  Parser f <*> Parser x = Parser h
    where
      h xs = f xs >>= \(ys, f') -> 
        x ys >>= \(zs, x') ->
        pure (zs, f' x')

我该怎么做?我尝试用手代替>>=,但总是在试图减少join时遇到困难 - 这也需要Monad

我也咨询了Parsec,但即使这样也没多大帮助:

instance Applicative.Applicative (ParsecT s u m) where
    pure = return
    (<*>) = ap

我提出这个问题的原因纯粹是自我教育。

2 个答案:

答案 0 :(得分:12)

这是不可能的。查看newtype

的内部
getParser :: [s] -> m ([s], a)

据推测,您希望将[s]传递给yx <*> y的输入。这正是Monad mApplicative m

之间的差异
  • Monad中,您可以使用一个计算的输出作为另一个计算的输入。
  • Applicative,你不能。

如果你做了一个有趣的伎俩,这是可能的:

Parser x <*> Parser y = Parser $
    \s -> (\(_, xv) (s', yv) -> (s', xv yv)) <$> x s <*> y s

但是,这几乎肯定不是您想要的定义,因为它并行解析xy

修复

  1. 您的ParserT可以非常轻松地Applicative

    newtype ParserT m s a = ParserT { runParser :: [s] -> m ([s], a) }
    -- or, equvalently
    newtype ParserT m s a = ParserT (StateT [s] m a)
    
    instance Monad m => Applicative (ParserT m s) where
        ...
    

    请注意,只要您未定义ParserT m s实例,Monad 就不是 Monad的实例。

  2. 您可以将剩余字符移到解析器之外:

    newtype ParserT m s a = ParserT { runParser :: [s] -> ([s], m a) }
    
    instance Applicative m => Applicative (ParserT m s) where
        ParserT x <*> ParserT y = ParserT $ \s ->
            let (s', x') = x s
                (s'', y') = y s'
            in x' <*> y'
        ...
    

答案 1 :(得分:3)

旨在尽可能使用Applicative的满分 - 它更清洁。

标题:您的解析器可以保持适用,但您的可能解析集合需要存储在Monad中。内部结构:使用monad。外部结构:适用。

您正在使用m ([s],a)来表示一堆可能的解析。当您解析下一个输入时,您希望它依赖于已经解析的内容,但是您使用的是m,因为可能有少于或多于一个可能的解析;您想要\([s],a) -> ...并使用它来制作新的m ([s],a)。该过程称为绑定并使用>>=或等效,因此您的容器绝对是Monad,无法逃脱。

使用monad作为容器并不是那么糟糕 - 它只是一个容器,你毕竟要保留一些东西。内部使用monad和monad之间有区别。在使用内部monad时,您的解析器可以适用。

What are the benefits of applicative parsing over monadic parsing?

如果你的解析器是适用的,它们会更简单,所以理论上你可以在组合它们时做一些优化,保留静态有关它们的功能的信息,而不是保持它们的实现。例如,

string "Hello World!" <|> string "Hello Mum!"
== (++) <$> string "Hello " <*> (string "World" <|> string "Mum!")

第二个版本比第一个版本好,因为它没有回溯。

如果你做了很多这样的事情,就像在正常表达式运行之前编译它一样,创建一个图形(有限状态自动机)并尽可能地简化它并消除一大堆低效的回溯。