[英]Error detection and reporting using Maybe
I am writing a propositional logic parser in Haskell. 我正在Haskell中编写一个命题逻辑解析器。 I am doing the parsing by hand for now as a learning exercise. 我现在正在手工解析作为学习练习。 Eventually I will tackle Parsec. 最终我将解决Parsec。 In the mean time, I am trying to wrap my head around Monads. 与此同时,我正试图绕着Monads。 In particular, I am using Maybe
to report errors from my parse
function. 特别是,我使用Maybe
来报告我的parse
函数中的错误。 My current trouble is with part of a helper function: 我目前的麻烦是辅助函数的一部分:
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
(For reference, the full parse
function can be found here .) (作为参考,可以在此处找到完整的parse
功能。)
This code parses a proposition of the form [ A | B ]
这段代码解析了[ A | B ]
形式的命题 [ A | B ]
where A
and B
are any arbitrary propositions. [ A | B ]
其中A
和B
是任意命题。 As you can see, I am using applicative style on the last line to propagate the Nothing
result if a previous recursive call results in a Nothing
. 正如您所看到的,如果先前的递归调用导致Nothing
,我在最后一行使用applicative样式来传播Nothing
结果。 This allowed me to take out a == Nothing
and b == Nothing
from the if
condition. 这允许我从if
条件中取出a == Nothing
和b == Nothing
。 How can I use the Applicative
or Monad
instance of Maybe
to colapse the rest of this if
? 我如何使用Applicative
或Monad
的实例Maybe
到COLAPSE这剩下的if
?
I will actually do the problem backwards: I will begin from the monadic solution and work backwards from it to the hand-rolled solution. 我实际上会向后解决这个问题:我将从monadic解决方案开始,然后从它向后工作到手动解决方案。 This will produce the same code that you would get if you arrived at the correct solution by hand. 如果您手动找到正确的解决方案,这将生成相同的代码。
The typical type signature of a monadic parser is of the form: monadic解析器的典型类型签名具有以下形式:
type Parser a = String -> Maybe (a, String)
Notice the slight difference with your form, where you have the final String
on the outside of the Maybe
. 注意与表单的细微差别,你在Maybe
的外面有最终的String
。 Both are valid, but I prefer this form more because I consider the leftovers String
invalid if the parse failed. 两者都有效,但我更喜欢这种形式,因为如果解析失败,我会认为剩余的String
无效。
This type is actually a special case of StateT
, which is defined as: 这种类型实际上是StateT
一个特例,它被定义为:
newtype StateT s m a = StateT { runStateT :: s -> m (a, s) }
Notice that if we choose: 请注意,如果我们选择:
s = String
m = Maybe
... we get back the Parser
type: ...我们回到Parser
类型:
type Parser a = StateT String Maybe a
-- or: type Parser = StateT String Maybe
What's cool about this is that we only need to define one parser by hand, which is the parser that retrieves a single character: 这很酷的是我们只需要手动定义一个解析器,它是检索单个字符的解析器:
anyChar :: Parser Char
anyChar = StateT $ \str -> case str of
[] -> Nothing
c:cs -> Just (c, cs)
Notice that if we removed the StateT
wrapper, the type of anyChar
would be: 请注意,如果我们删除了StateT
包装的类型, anyChar
将是:
anyChar :: String -> Maybe (Char, String)
When we wrap it in StateT
it becomes: 当我们将它包装在StateT
它变为:
anyChar :: StateT String Maybe Char
... which is just Parser Char
. ......这只是Parser Char
。
Once we have that primitive parser, we can define all the other parsers using StateT
's Monad
interface. 一旦我们有了这个原始解析器,我们就可以使用StateT
的Monad
接口定义所有其他解析器。 For example, let's define a parser that matches a single character: 例如,让我们定义一个匹配单个字符的解析器:
import Control.Monad
char :: Char -> Parser ()
char c' = do
c <- anyChar
guard (c == c')
That was easy! 那很简单! guard
requires a MonadPlus
instance for our monad, but we already have one. guard
需要一个MonadPlus
实例用于我们的monad,但我们已经有了一个。 The reason why is thanks to the following two MonadPlus
instances: 原因是由于以下两个MonadPlus
实例:
instance (MonadPlus m) => MonadPlus (StateT s m) where ...
instance MonadPlus Maybe where ...
The combination of these two instances means that StateT s Maybe
automatically implements MonadPlus
, so we can use guard
and it will just magically do "the right thing". 这两个实例的组合意味着StateT s Maybe
自动实现MonadPlus
,因此我们可以使用guard
,它只会神奇地做“正确的事情”。
With those two parser in hand, your final parser becomes very easy to write: 有了这两个解析器,你的最终解析器变得非常容易编写:
data Wff = Or Char Char deriving (Show)
parseWff :: Parser Wff
parseWff = do
char '['
a <- anyChar
char '|'
b <- anyChar
char ']'
return (Or a b)
That's much clearer and easier to understand what is going on. 这更加清晰,更容易理解发生了什么。 It also works: 它也有效:
>>> runStateT parseWff "[A|B]"
Just (Or 'A' 'B',"")
This brings us to your original question: How do you hand-write the same behavior? 这将我们带到您原来的问题:您如何手写相同的行为? We will work backwards from the Monad
and MonadPlus
instances to deduce what they are doing under the hood for us. 我们将从Monad
和MonadPlus
实例向后工作,为我们演绎他们在MonadPlus
做的事情。
To do this, we must deduce what the Monad
and MonadPlus
instances for StateT
reduce to when its base monad is Maybe
. 要做到这一点,我们必须推断出的Monad
和MonadPlus
的实例StateT
减少时,其基础是单子Maybe
。 Let's begin from the Monad
instance for StateT
: 让我们从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
Notice that it is defined in generically terms of the base monad. 请注意,它是以基本monad的一般术语定义的。 This means we also need the Monad
instance for Maybe
to derive what the above code does: 这意味着我们还需要Maybe
的Monad
实例来推导上面的代码:
instance Monad Maybe where
return = Just
m >>= f = case m of
Nothing -> Nothing
Just a -> f a
If we substitute the Maybe
monad instance into the StateT
monad instance we get: 如果我们将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
We can do the same thing to derive the Monad
instance for StateT s Maybe
. 我们可以为StateT s Maybe
派生Monad
实例做同样的事情。 We just have to take the MonadPlus
instances for StateT
and `Maybe: 我们只需要为StateT
和“可能”采用MonadPlus
实例:
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
... and combine them into one: ......并将它们组合成一个:
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
Now we can derive what our parsers are doing under the hood. 现在我们可以得出我们的解析器在幕后做的事情。 Let's begin with the char
parser: 让我们从char
解析器开始:
char c' = do
c <- anyChar
guard (c == c')
This desugars to: 这令人厌恶:
char c' = anyChar >>= \c -> guard (c == c')
We just derived what (>>=)
does for StateT s Maybe
, so lets substitute that in: 我们只是派生了什么(>>=)
为StateT s Maybe
做了StateT s Maybe
,所以让我们替换它:
char c' = StateT $ \str0 -> case runStateT anyChar str0 of
Nothing -> Nothing
Just (a, str1) -> runStateT ((\c -> guard (c == c')) a) str1
We already know the definition of anyChar
, so let's substitute that in: 我们已经知道了anyChar
的定义,所以让我们用它代替:
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
We also know that runStateT
is the inverse of StateT
, so: 我们也知道, 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
We can then apply the lambda to str0
: 然后我们可以将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
Now we distribute the outer case statement over the inner case statement: 现在我们在内部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
... and evaluate the case statements: ......并评估案例陈述:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT ((\c -> guard (c == c')) c) cs
Then we can apply the lambda to c
: 然后我们可以将lambda应用于c
:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (guard (c == c')) cs
To simplify that further, we need to know what guard
does. 为了进一步简化,我们需要知道guard
作用。 Here is the source code for it: 这是它的源代码:
guard pred = if pred then return () else mzero
We already know what return
and mzero
for StateT s Maybe
are, so let's substitute them in: 我们已经知道StateT s Maybe
return
和mzero
是什么,所以让我们替换它们:
guard pred = if pred then StateT (\s -> Just ((), s)) else StateT (\_ -> Nothing)
Now we can inline that into our function: 现在我们可以将其内联到我们的函数中:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> runStateT (if (c == c')
then StateT (\s -> Just ((), s))
else StateT (\_ -> Nothing) ) cs
If we distribute runStateT
over that we get: 如果我们分配runStateT
,我们得到:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> (if (c == c')
then (\s -> Just ((), s))
else (\_ -> Nothing) ) cs
Similarly, we can apply both branches to cs
: 同样,我们可以将两个分支应用于cs
:
char c' = StateT $ \str0 -> case str0 of
[] -> Nothing
c:cs -> if (c == c') then Just ((), cs) else Nothing
That's the equivalent code we would have written by hand had we not used the Monad
or MonadPlus
instances at all. 如果我们根本没有使用Monad
或MonadPlus
实例,那就是我们手写的等效代码。
I will now repeat this process for the last function, but leave the derivation as an exercise for you: 我现在将为最后一个函数重复此过程,但将派生作为练习:
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
... but we can further simplify that to: ......但我们可以进一步简化为:
parseWff = StateT $ \str0 -> case str0 of
'[':c2:'|':c4:']':str5 -> Just (Or c2 c4, str5)
_ -> Nothing
Notice that, unlike the function you wrote, this doesn't use any partial functions like tail
or incomplete pattern matches. 请注意,与您编写的函数不同,它不使用任何部分函数,如tail
或不完整模式匹配。 Also, the code you wrote doesn't compile, but even if it did, it would still give the wrong behavior. 此外,您编写的代码不会编译,但即使它编译,它仍然会给出错误的行为。
You can use a function from Control.Monad
called guard
. 您可以使用Control.Monad
名为guard
的函数。 This has a slightly odd type: 这有点奇怪的类型:
guard :: MonadPlus m => Bool -> m ()
MonadPlus
covers all monads that have some "empty" case. MonadPlus
涵盖所有具有“空”案例的monad。 For lists, this is []
; 对于列表,这是[]
; for Maybe
it is Nothing
. 因为它Maybe
Nothing
。 guard
takes a boolean; guard
需要一个布尔值; if it is False
, it evaluates to this empty value; 如果为False
,则计算为该空值; otherwise it evaluates to return ()
. 否则它评估为return ()
。 This behavior is mostly useful in do
notation: 此行为是最有用do
记号:
x = do guard (not $ null rest' || null rest'' || head rest' /= '|' || head rest'' /= ']')
Or <$> a <*> b
What happens here is simple. 这里发生的事情很简单。 If the condition evaluates to True
, guard returns Just ()
, which is then ignored in favor of Or <$> a <*> b
(since that's how do
notation works). 如果条件评估为True
,门卫返回Just ()
然后将其赞成忽略Or <$> a <*> b
(因为这是如何do
的符号的作品)。 However, if the condition is False
, guard
returns Nothing
, which propagates through the rest of the do
notation to give you an end result of Nothing
: exactly what you wanted. 然而,如果条件为False
, guard
返回Nothing
,其传播通过其余do
记号给你的最终结果Nothing
:你想要什么。
To make the code more readable, I would also pull the condition out into its own variable in a where
block. 为了使代码更具可读性,我还将条件拉出到where
块中的自己的变量中。
Based on the answer by @TikhonJelvis , I revamped my whole parse
function. 根据@TikhonJelvis的回答 ,我修改了我的整个parse
功能。 (The parse'
function from the OP is in the where
clause of parse
.) The first revision uses do notation and `guard (来自OP的parse'
函数位于parse
的where
子句中。)第一个修订版使用do符号和`guard
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
Some further experimentation helped me figure out that I can replace all but one of the uses of guard
with direct pattern matching: 一些进一步的实验帮助我弄清楚我可以用直接模式匹配替换除guard
所有用途:
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.