我无法弄清楚如何为此解析器实现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
我提出这个问题的原因纯粹是自我教育。
答案 0 :(得分:12)
这是不可能的。查看newtype
:
getParser :: [s] -> m ([s], a)
据推测,您希望将[s]
传递给y
中x <*> y
的输入。这正是Monad m
和Applicative m
:
Monad
中,您可以使用一个计算的输出作为另一个计算的输入。Applicative
,你不能。如果你做了一个有趣的伎俩,这是可能的:
Parser x <*> Parser y = Parser $
\s -> (\(_, xv) (s', yv) -> (s', xv yv)) <$> x s <*> y s
但是,这几乎肯定不是您想要的定义,因为它并行解析x
和y
。
您的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
的实例。
您可以将剩余字符移到解析器之外:
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!")
第二个版本比第一个版本好,因为它没有回溯。
如果你做了很多这样的事情,就像在正常表达式运行之前编译它一样,创建一个图形(有限状态自动机)并尽可能地简化它并消除一大堆低效的回溯。