简体   繁体   English

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

[英]How do I implement an Applicative instance for a parser without assuming Monad?

I can't figure out how to implement an Applicative instance for this parser: 我无法弄清楚如何为此解析器实现Applicative实例:

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

without assuming Monad m . 没有假设Monad m I expected to only have to assume Applicative m , since the Functor instance only has to assume Functor m . 我希望只需要假设Applicative m ,因为Functor实例只需要假设Functor m I finally ended up with: 我终于结束了:

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')

How do I do this? 我该怎么做呢? I tried substituting in for >>= by hand, but always wound up getting stuck trying to reduce a join -- which would also require Monad . 我尝试用手代替in >>= ,但总是因为尝试减少join而陷入困境 - 这也需要Monad

I also consulted Parsec , but even that wasn't much help: 我也咨询了Parsec ,但即使这样也没多大帮助:

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

My reasons for asking this question are purely self-educational. 我提出这个问题的原因纯粹是自我教育。

It's not possible. 这是不可能的。 Look at the inside of your newtype : 看看你的内部newtype

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

Presumably, you want to pass [s] to the input of y in x <*> y . 据推测,您希望将[s]传递给x <*> yy的输入。 This is exactly the difference between Monad m and Applicative m : 这正是Monad mApplicative m之间的区别:

  • In Monad you can use the output of one computation as the input to another. Monad您可以使用一个计算的输出作为另一个计算的输入。
  • In Applicative , you cannot. Applicative ,你不能。

It's possible if you do a funny trick: 如果你做一个有趣的伎俩是可能的:

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

However, this is almost certainly not the definition that you want, since it parses x and y in parallel. 但是,这几乎肯定不是您想要的定义,因为它并行解析xy

Fixes 修复

  1. Your ParserT can be Applicative quite easily: 您的ParserT可以很容易地Applicative

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

    Note that ParserT ms is not an instance of Monad as long as you don't define the Monad instance. 需要注意的是ParserT ms 不是一个实例Monad ,只要你不定义Monad实例。

  2. You can move the leftover characters outside the parser: 您可以在解析器外移动剩余的字符:

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

Full marks for aiming to use Applicative as much as possible - it's much cleaner. 旨在尽可能使用Applicative的满分 - 它更清洁。

Headline: Your parser can stay Applicative, but your collection of possible parses need to be stored in a Monad. 标题: 您的解析器可以保持适用,但您可能的解析集合需要存储在Monad中。 Internal structure: uses a monad. 内部结构:使用monad。 External structure: is applicative. 外部结构:适用。

You're using m ([s],a) to represent a bunch of possible parses. 你使用m ([s],a)来表示一堆可能的解析。 When you parse the next input, you want it to depend on what's already been parsed, but you're using m because there's potentially less than or more than one possible parse; 解析下一个输入时,您希望它依赖于已经解析的内容,但是您使用的是m因为可能有少于或多于一个可能的解析; you want to do \\([s],a) -> ... and work with that to make a new m ([s],a) . 你想做\\([s],a) -> ...并使用它来制作一个新的m ([s],a) That process is called binding and uses >>= or equivalent, so your container is definitely a Monad, no escape. 该过程称为绑定并使用>>=或等效,因此您的容器绝对是Monad,无法逃脱。

It's not all that bad using a monad for your container - it's just a container you're keeping some stuff in after all. 使用monad作为你的容器并不是那么糟糕 - 它只是一个容器,你毕竟保留了一些东西。 There's a difference between using a monad internally and being a monad. 内部使用monad和monad之间有区别。 Your parsers can be applicative whilst using a monad inside. 在使用monad时,你的解析器可以适用。

See What are the benefits of applicative parsing over monadic parsing? 请参阅应用解析比monadic解析有什么好处? .

If your parsers are applicative, they're simpler, so in theory you can do some optimisation when you combine them, by keeping static information about what they do instead of keeping their implementation . 如果你的解析器是适用的,它们就更简单了,所以理论上你可以在组合它们时做一些优化,保留静态信息,而不是保持它们的实现 For example, 例如,

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

The second version is better than the first because it does no backtracking. 第二个版本比第一个版本更好,因为它没有回溯。

If you do a lot of this, it's like when a regular expression is compiled before it's run, creating a graph (finite state automaton) and simplifying it as much as possible and eliminating a whole load of inefficient backtracking. 如果你做了很多这样的事情,就像在正常表达式运行之前编译它一样,创建一个图形(有限状态自动机)并尽可能地简化它并消除一大堆低效的回溯。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM