为了使用do 表示法,您的Parser 必须是Monad 的一个实例:
instance Monad Parser where
return :: a -> Parser a
return = -- ...
(>>=) :: Parser a -> (a -> Parser b) -> Parser b
p >>= f = -- ...
编译器需要你填写return和>>=的定义。
do 表示法是一种语法糖,可用于 >>=(发音为“bind”)。例如,您的代码脱糖为:
p :: Parser (Char, Char)
p = item >>= \x ->
item >>= \_ ->
item >>= \y ->
return (x,y)
或者,使用更明确的括号:
p = item >>= (\x -> item >>= (\_ -> item >>= (\y -> return (x,y))))
>>= 描述了如何将Parser a 与函数a -> Parser b 结合起来创建一个新的Parser b。
使用您对Parser 的定义,一个工作的Monad 实例是
instance Monad Parser where
return a = P $ \s -> [(a,s)]
p >>= f = P $ concatMap (\(a,s') -> runParser (f a) s') . runParser p
-- which is equivalent to
-- p >>= f = P $ \s -> [(b,s'') | (a,s') <- runParser p s, (b,s'') <- runParser (f a) s']
考虑>>= 在p :: Parser a 和函数f :: a -> Parser b 方面的作用。
-
当展开时,p 接受 String,并返回 (a,String) 对的列表
runParser p :: String -> [(a,String)]
-
对于每个(a,String) 对,我们可以在a 上运行f 以获取新的解析器q:
map go . runParser p :: String -> [(Parser b,String)]
where go :: (a, String) -> (Parser b, String)
go (a,s') = let q = f a in (q, s')
-
如果我们打开 q,我们会得到一个函数,它接受 String 并返回 (b, String) 对的列表:
map go . runParser p :: String -> [(String -> [(b,String)],String)]
where go :: (a, String) -> (String -> [(b,String)],String)
go (a,s') = let q = f a in (runParser q, s')
-
我们可以在与a 配对的String 上运行该函数,以立即获取我们的`(b, String) 对列表:
map go . runParser p :: String -> [[(b,String)]]
where go :: (a, String) -> [(b,String)]
go (a,s') = let q = f a in runParser q s'
-
如果我们将生成的列表列表展平,我们会得到一个 String -> [(b,String)],它只是被解包的 Parser b
concat . map go . runParser p :: String -> [(b,String)]
where go :: (a, String) -> [(b,String)]
go (a,s') = let q = f a in runParser q s'