我试图弄清楚如何构建一个纯粹适用的解析器"基于简单的parser实现。解析器在其实现中不会使用monad。我先前问了这个问题,但错误地构了它,所以我再试一次。
以下是基本类型及其Functor
,Applicative
和Alternative
实现:
newtype Parser a = Parser { parse :: String -> [(a,String)] }
instance Functor Parser where
fmap f (Parser cs) = Parser (\s -> [(f a, b) | (a, b) <- cs s])
instance Applicative Parser where
pure = Parser (\s -> [(a,s)])
(Parser cs1) <*> (Parser cs2) = Parser (\s -> [(f a, s2) | (f, s1) <- cs1 s, (a, s2) <- cs2 s1])
instance Alternative Parser where
empty = Parser $ \s -> []
p <|> q = Parser $ \s ->
case parse p s of
[] -> parse q s
r -> r
item
函数从流中取出一个字符:
item :: Parser Char
item = Parser $ \s ->
case s of
[] -> []
(c:cs) -> [(c,cs)]
此时,我想实施digit
。我当然可以这样做:
digit = Parser $ \s ->
case s of
[] -> []
(c:cs) -> if isDigit c then [(c, cs)] else []
但我正在复制item
的代码。我希望根据digit
实施item
。
如何实现digit
,使用item
从流中取出一个字符,然后检查字符是否为数字而不将monadic概念引入实现?
答案 0 :(得分:2)
Functors允许您对某些事物值进行操作。例如,如果您有一个列表[1,2,3]
,则可以更改内容。请注意,Functors不允许更改结构。 map
无法更改列表的长度。
应用程序允许您组合结构,并且内容以某种方式汇集在一起。 但值不能改变影响结构。
即,给定item
,我们可以更改其结构,我们可以更改其内容,但内容不能更改结构。我们不能选择在某些内容上失败而不是其他内容。
如果有人知道如何更正式和可证明地说明这一点,我全神贯注(这可能与自由定理有关)。
答案 1 :(得分:2)
首先,让我们写下我们目前掌握的所有工具:
-- 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
排除在外。
问题是我们需要检查Char
item
可能解析和更改上下文。我们需要像Char -> Parser Char
但我们没有将Parser
或parse
排除在外!
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)
,它只能通过类似连接的函数折叠。这就是为什么monads are more powerful than applicative。
话虽如此,如果你先使用更通用的功能,你可以绕过所有的monad要求:
satisfy :: (Char -> Bool) -> Parser Char
satisfy = Parser $ \s ->
case s of
(c:cs) | p c -> [(c, cs)]
_ -> []
然后,您可以根据item
定义digit
和satisfy
:
item = satisfy (const True)
digit = satisfy isDigit
那样digit
不必检查先前解析器的结果。