Parsing 使用Maybe进行错误检测和报告
我正在用Haskell编写一个命题逻辑解析器。作为一个学习练习,我现在正在手工解析。最终我将解决Parsec。与此同时,我正试图把我的头绕在单子身上。特别是,我使用Parsing 使用Maybe进行错误检测和报告,parsing,haskell,text-parsing,Parsing,Haskell,Text Parsing,我正在用Haskell编写一个命题逻辑解析器。作为一个学习练习,我现在正在手工解析。最终我将解决Parsec。与此同时,我正试图把我的头绕在单子身上。特别是,我使用Maybe报告parse函数中的错误。我当前的问题是助手函数的一部分: parse' :: String -> (Maybe Wff, String) parse' ('[':rest) = (x, if null rest'' then ""
Maybe
报告parse
函数中的错误。我当前的问题是助手函数的一部分:
parse' :: String -> (Maybe Wff, String)
parse' ('[':rest) = (x, if null rest''
then ""
else tail rest'')
where (a, rest') = parse' rest
(b, rest'') = parse' (if null rest'
then ""
else tail rest')
x = if null rest'
|| null rest''
|| head rest' /= '|'
|| head rest'' /= ']'
then Nothing
else Or <$> a <*> b
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (if (c == c')
then StateT (\s -> Just ((), s))
else StateT (\_ -> Nothing) ) cs
parse'::String->(可能是Wff,String)
解析“(”[”:rest)=(x,如果rest为空”
然后“
else尾架“”)
其中(a,rest')=解析“rest”
(b,rest“”)=parse'(如果rest为空)
然后“
其他尾座')
x=如果为空rest'
||空rest“”
||头枕“/=”|”
||头枕'/=']'
那就什么都没有了
还是b
(为了便于参考,可以找到完整的parse
函数。)
此代码解析形式为
[a | B]
的命题,其中a
和B
是任意命题。如您所见,如果前面的递归调用导致无
结果,我将使用最后一行中的应用程序样式来传播无
结果。这允许我从if
条件中取出a==Nothing
和b==Nothing
。如果,我如何使用的应用程序
或Monad
实例来关闭该的其余部分。这有点奇怪:
guard :: MonadPlus m => Bool -> m ()
MonadPlus
覆盖所有具有“空”大小写的monad。对于列表,这是[]
;对于可能
而言,它是无
<代码>保护
采用布尔值;如果它是False
,则计算为该空值;否则它的计算结果为return()
。此行为在do
表示法中最有用:
x = do guard (not $ null rest' || null rest'' || head rest' /= '|' || head rest'' /= ']')
Or <$> a <*> b
x=do-guard(不是$null-rest'| | null-rest'| | head-rest'/='|'/='|'.| head-rest'/='])
还是b
这里发生的事情很简单。如果条件的计算结果为True
,guard将返回Just()
,然后将其忽略以支持或b
(因为这就是do
符号的工作方式)。但是,如果条件为False
,guard
返回Nothing
,它通过do
符号的其余部分传播,为您提供Nothing
的最终结果:正是您想要的结果
为了使代码更具可读性,我还将把条件提取到where
块中它自己的变量中。我将实际向后做这个问题:我将从一元解开始,并从它向后工作到手动滚动解。这将产生与手工获得正确解决方案时相同的代码
一元解析器的典型类型签名的形式如下:
type Parser a = String -> Maybe (a, String)
注意表单的细微差别,在Maybe
的外部有最后一个字符串。两者都是有效的,但我更喜欢这个形式,因为我认为如果解析失败,剩下的代码<代码>字符串>代码>无效。
这种类型实际上是StateT
的特例,其定义如下:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
请注意,如果我们选择:
s = String
m = Maybe
。。。我们返回解析器
类型:
type Parser a = StateT String Maybe a
-- or: type Parser = StateT String Maybe
最酷的是,我们只需要手动定义一个解析器,即检索单个字符的解析器:
anyChar :: Parser Char
anyChar = StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs)
import Control.Monad
char :: Char -> Parser ()
char c' = do
c <- anyChar
guard (c == c')
请注意,如果我们删除了StateT
包装,那么anyChar
的类型将是:
anyChar :: String -> Maybe (Char, String)
当我们将其包装在StateT
中时,它会变成:
anyChar :: StateT String Maybe Char
。。。这只是解析器字符
一旦我们有了这个基本解析器,我们就可以使用StateT
的Monad
接口定义所有其他解析器。例如,让我们定义一个匹配单个字符的解析器:
anyChar :: Parser Char
anyChar = StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs)
import Control.Monad
char :: Char -> Parser ()
char c' = do
c <- anyChar
guard (c == c')
这两个实例的组合意味着StateT s可能会
自动实现MonadPlus
,因此我们可以使用guard
,它会神奇地做“正确的事情”
有了这两个解析器,您的最终解析器变得非常容易编写:
data Wff = Or Char Char deriving (Show)
parseWff :: Parser Wff
parseWff = do
char '['
a <- anyChar
char '|'
b <- anyChar
char ']'
return (Or a b)
反向工作
这就引出了您最初的问题:如何手写相同的行为?我们将从Monad
和MonadPlus
实例向后推导出它们在幕后为我们做什么
为此,我们必须推断StateT
的Monad
和MonadPlus
实例在其基本Monad为时减少到什么程度。让我们从StateT
的Monad
实例开始:
instance (Monad m) => Monad (StateT s m) where
return r = StateT (\s -> return (r, s))
m >>= f = StateT $ \s0 -> do
(a, s1) <- runStateT m s0
runStateT (f a) s1
如果我们将Maybe
monad实例替换为StateT
monad实例,我们得到:
instance Monad (StateT s Maybe) where
return r = StateT (\s -> Just (r, s))
m >>= f = StateT $ \s0 -> case runStateT m s0 of
Nothing -> Nothing
Just (a, s1) -> runStateT (f a) s1
我们可以做同样的事情来派生StateT s的Monad
实例。我们只需将MonadPlus
实例作为StateT
的实例,然后`也许:
instance (MonadPlus m) => MonadPlus (StateT s m) where
mzero = StateT (\_ -> mzero)
mplus (StateT f) (StateT g) = StateT (\s -> mplus (f s) (g s))
instance MonadPlus Maybe where
mzero = Nothing
mplus m1 m2 = case m1 of
Just a -> Just a
Nothing -> case m2 of
Just b -> Just b
Nothing -> Nothing
。。。并将它们合并为一个:
instance MonadPlus (StateT s Maybe) where
mzero = StateT (\_ -> Nothing)
mplus (StateT f) (StateT g) = StateT $ \s -> case f s of
Just a -> Just a
Nothing -> case g s of
Just b -> Just b
Nothing -> Nothing
现在我们可以推导出解析器在引擎盖下所做的工作。让我们从char
解析器开始:
char c' = do
c <- anyChar
guard (c == c')
我们刚刚推导出了(>>=)
对状态的作用,可能是
,因此让我们用以下内容代替:
char c' = StateT $ \str0 -> case runStateT anyChar str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
char c' = StateT $ \str0 -> case runStateT (StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs) ) str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
我们已经知道了anyChar
的定义,因此让我们将其替换为:
char c' = StateT $ \str0 -> case runStateT anyChar str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
char c' = StateT $ \str0 -> case runStateT (StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs) ) str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
我们还知道runStateT
与StateT
相反,因此:
char c' = StateT $ \str0 -> case (\str -> case str of
[] -> Nothing
c:cs -> Just (c, cs) ) str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
然后,我们可以将lambda应用于str0
:
char c' = StateT $ \str0 -> case (case str0 of
[] -> Nothing
c:cs -> Just (c, cs) ) of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
现在,我们将外部case语句分布在内部case语句上:
char c' = StateT $ \str0 -> case str0 of
[] -> case Nothing of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
c:cs -> case Just (c, cs) of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
。。。并评估案例陈述:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT ((\c -> guard (c == c')) c) cs
然后我们可以将lambda应用于c
:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (guard (c == c')) cs
为了进一步简化,我们需要
guard pred = if pred then StateT (\s -> Just ((), s)) else StateT (\_ -> Nothing)
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (if (c == c')
then StateT (\s -> Just ((), s))
else StateT (\_ -> Nothing) ) cs
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> (if (c == c')
then (\s -> Just ((), s))
else (\_ -> Nothing) ) cs
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> if (c == c') then Just ((), cs) else Nothing
parseWff = do
char '['
a <- anyChar
char '|'
b <- anyChar
char ']'
return (Or a b)
parseWff = StateT $ \str0 -> case str0 of
[] -> Nothing
c1:str1 -> if (c1 == '[')
then case str1 of
[] -> Nothing
c2:str2 -> case str2 of
[] -> Nothing
c3:str3 -> if (c3 == '|')
then case str3 of
[] -> Nothing
c4:str4 -> case str4 of
[] -> Nothing
c5:str5 -> if (c5 == ']')
then Just (Or c2 c4, str5)
else Nothing
else Nothing
else Nothing
parseWff = StateT $ \str0 -> case str0 of
'[':c2:'|':c4:']':str5 -> Just (Or c2 c4, str5)
_ -> Nothing
parse :: String -> Maybe Wff
parse s = do
(x, rest) <- parse' s
guard $ null rest
Just x
where parse' ('~':rest) = do
guard . not $ null rest
(a, rest') <- parse' rest
Just (Not a, rest')
parse' ('[':rest) = do
guard . not $ null rest
(a, rest') <- parse' rest
guard . not $ null rest'
guard $ head rest' == '|'
(b, rest'') <- parse' $ tail rest'
guard . not $ null rest''
guard $ head rest'' == ']'
Just (a `Or` b, tail rest'')
parse' (c:rest) = do
guard $ isLower c
Just (Var c, rest)
parse' [] = Nothing
parse :: String -> Maybe Wff
parse s = do
(x, "") <- parse' s
Just x
where parse' ('~':rest) = do
(a, rest') <- parse' rest
Just (Not a, rest')
parse' ('[':rest) = do
(a, ('|':rest')) <- parse' rest
(b, (']':rest'')) <- parse' rest'
Just (a `Or` b, rest'')
parse' (c:rest) = do
guard $ isLower c
Just (Var c, rest)
parse' [] = Nothing