首先,让我们写下我们目前手头上的所有工具:
-- Data constructor
Parser :: (String -> [(a, String)]) -> Parser a
-- field accessor
parse :: Parser a -> String -> [(a, String)]
-- instances, replace 'f' by 'Parser'
fmap :: Functor f => (a -> b) -> f a -> f b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
pure :: Applicative f => a -> f a
-- the parser at hand
item :: Parser Char
-- the parser we want to write with item
digit :: Parser Char
digit = magic item
-- ?
magic :: Parser Char -> Parser Char
当前真正的问题是“什么是magic
“?我们能用的东西就这么多。它的类型表明fmap
,但我们可以排除这种可能性。我们所能提供的只是一些功能a -> b
,但是没有f :: Char -> Char
这使得fmap f
表明失败。
关于什么(<*>)
,这有帮助吗?好吧,再说一遍,答案是否定的。我们在这里唯一能做的就是采取(a -> b)
脱离上下文并应用它;无论这在给定的上下文中意味着什么Applicative
。我们可以统治pure
out.
问题是我们需要检查Char
that item
可能会解析and改变上下文。我们需要类似的东西Char -> Parser Char
但我们没有统治Parser
or parse
out!
magic p = Parser $ \s ->
case parse p s of -- < item will be used here
[(c, cs)] -> if isDigit c then [(c, cs)] else []
_ -> []
是的,我知道,这是重复的代码,但现在它正在使用item
。它正在使用item
在检查角色之前。这是我们可以使用的唯一方法item
这里。现在,隐含着某种顺序:item
必须先成功digit
可以做它的工作。
或者,我们可以尝试这样:
digit' c :: Char -> Parser Char
digit' c = if isDigit c then pure c else empty
但是之后fmap digit' item
会有这样的类型Parser (Parser Char)
,它只能通过类似 join 的函数折叠。这就是为什么monad 比 applicative 更强大 https://stackoverflow.com/q/34932624/1139697.
话虽这么说,如果您首先使用更通用的函数,您可以绕过所有 monad 要求:
satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s ->
case s of
(c:cs) | p c -> [(c, cs)]
_ -> []
然后您可以定义两者item
and digit
按照satisfy
:
item = satisfy (const True)
digit = satisfy isDigit
那样digit
不必检查先前解析器的结果。