2014-07-26 35 views
3

假设你有一组精心设计的功能,并且知道你的设计,你可以说功能和参数的组合不会发生。如果需要的话,这是编译器可以实际推动的东西。如何处理非穷尽模式匹配的误报?

为了清楚起见,拿这个例子中(不要告诉我用map,这是一个例子):

processAll :: [Int] -> [Int] 
processAll [] = [] 
processAll a = let (x, xs) = processOne a in x:processAll xs 
    where 
     processOne (x:xs) = (x+1,xs) 

在这个例子中,这是很明显,processOne不能与被调用空的清单。与GHC编译并加入-Wall警告说:

Pattern match(es) are non-exhaustive 
In an equation for `processOne': Patterns not matched: [] 

当然,我不希望禁用,一般这样的警告,因为我实际上可能错过了一个模式匹配其他地方。然而,我希望ghc能够推断出这个模式列表在其域中是详尽的。

的替代解决方案禁用警告将是:

processAll :: [Int] -> [Int] 
processAll [] = [] 
processAll a = let (x, xs) = processOne a in x:processAll xs 
    where 
     processOne (x:xs) = (x+1,xs) 
     processOne _ = error "processor overheat - explosion imminent" 

其是两个冗余(因为processOne []将导致error反正)和繁琐的。

一般人应该如何处理这种情况?继续并在每个不可能的情况下添加error消息?


在这个特殊的例子,我知道有更好的方法通过having the caller match on the pattern来处理这一点,例如。所以,如果你想在这里是另外一个例子是从词法分析器我写,你可以运行,也非常简单摘录:

import Data.Char (isNumber, isAlpha) 
import Control.Monad 

data TokenType = ParenOpen   -- (
       | ParenClose  --) 
       | Plus    -- + 
       | Number String  -- A number 
       | Variable String -- Anything else 
       | End    -- End of the stream 
       deriving (Show, Eq) 

-- content is the content of a file from a line and column on 
type Content = (String, Int, Int) 

-- a token is a token and its position as matched by the lexer 
type Token = (TokenType, Int, Int) 

lexer :: String -> [Token] 
lexer = lexAll . (\s -> (s, 1, 1)) 
    where 
     -- make a maybe value based on a Bool 
     makeMaybe :: Bool -> a -> Maybe a 
     makeMaybe p x = if p then return x else Nothing 

     -- advance the content by one, taking care of line and column numbers 
     advance :: Content -> Content 
     advance (x:xs, l, c) = (xs, l', c') 
          where 
           l' = if x == '\n' then l + 1 else l 
           c' = if x == '\n' then 1 else c + 1 

     -- advance the content by n 
     advance' n content = iterate advance content !! n 

     -- match a single character 
     matchExact :: Char -> Content -> Maybe Content 
     matchExact y [email protected](x:_, _, _) = makeMaybe (x == y) $ advance content 

     -- match while pattern holds for characters 
     matchPattern :: (Char -> Bool) -> Content -> Maybe (String, Content) 
     matchPattern p [email protected](xs, _, _) = makeMaybe (len > 0) (pfx, advance' len content) 
            where 
             pfx = takeWhile p xs 
             len = length pfx 

     matchParenOpen = matchExact '(' >=> (\c -> return (ParenOpen, c)) 
     matchParenClose = matchExact ')' >=> (\c -> return (ParenClose, c)) 
     matchPlus = matchExact '+' >=> (\c -> return (Plus, c)) 
     matchNumber = matchPattern isNumber >=> (\(s, c) -> return (Number s, c)) 
     matchVariable = matchPattern isAlpha >=> (\(s, c) -> return (Variable s, c)) 

     lexOne :: Content -> (Token, Content) 
     lexOne [email protected]([], l, c) = ((End, l, c), cur) 
     lexOne [email protected](_, l, c) = let tokenMatchers = [matchParenOpen, 
                 matchParenClose, 
                 matchPlus, 
                 matchNumber, 
                 matchVariable 
                ] in 
           case msum $ map ($ cur) tokenMatchers of 
            -- if nothing could be matched, generate an error and skip the character 
            Nothing -> lexOne $ advance cur 
            -- otherwise, this is an interesting token 
            Just (t, cnt) -> ((t, l, c), cnt) 

     lexAll :: Content -> [Token] 
     lexAll ([], _, _) = [] 
     lexAll content = token:lexAll rest 
        where 
         (token, rest) = lexOne content 

main :: IO() 
main = getContents >>= putStrLn . unlines . map (\(t, l, c) -> show l ++ ":" ++ show C++ ": " ++ show t) . lexer 

在上面的例子中,lexOne确保没有任何的match*功能,并因此advance*函数被赋予一个Content空字符串。 ghc警告:

Pattern match(es) are non-exhaustive 
In an equation for `advance': Patterns not matched: ([], _, _) 

Pattern match(es) are non-exhaustive 
In an equation for `matchExact': Patterns not matched: _ ([], _, _) 

我可以肯定地说,绝对不会发生。处理这个问题的正确方法是什么?

+3

展开cons构造函数,使它再次合计。 'processOne x xs =(x + 1,xs)'一般来说,争取所有函数在它们的类型上是全部的 - 即使它们的常规域与它们的形式域相同。 – luqui

+0

@luqui,这是一般的好建议,我同意。但如果你快速浏览一下我的真实例子,你会发现它可能会让事情变得有些尴尬。很多只需要一个Content的函数就不得不采用'head tail(line,column)'。基本上,我定义了“内容”,以便代码更清晰。 – Shahbaz

回答

2

为什么不只是为NonEmptyContent添加一个类型?

module SO24967745 where 
import Control.Monad 
import Data.Char 

data TokenType = ParenOpen   -- (
       | ParenClose  --) 
       | Plus    -- + 
       | Number String  -- A number 
       | Variable String -- Anything else 
       | End    -- End of the stream 
       deriving (Show, Eq) 

-- content is the content of a file from a line and column on 
type Content = (String, Int, Int) 
type NonEmptyContent = (Char, String, Int, Int) 

-- a token is a token and its position as matched by the lexer 
type Token = (TokenType, Int, Int) 

lexer :: String -> [Token] 
lexer = lexAll . (\s -> (s, 1, 1)) 
    where 
     -- make a maybe value based on a Bool 
     makeMaybe :: Bool -> a -> Maybe a 
     makeMaybe p x = if p then return x else Nothing 

     toNonEmptyContent :: Content -> Maybe NonEmptyContent 
     toNonEmptyContent ([], _, _) = Nothing 
     toNonEmptyContent (x:xs,l,c) = Just (x,xs,l,c) 

     toContent :: NonEmptyContent -> Content 
     toContent (x, xs, l, c) = (x:xs, l, c) 

     -- advance the content by one, taking care of line and column numbers 
     advance :: NonEmptyContent -> Content 
     advance (x, xs, l, c) = (xs, l', c') 
          where 
           l' = if x == '\n' then l + 1 else l 
           c' = if x == '\n' then 1 else c + 1 

     -- advance the content by n 
     advance' :: Int -> NonEmptyContent -> Maybe Content 
     advance' n = foldr (>=>) Just (replicate n (fmap advance . toNonEmptyContent)) . toContent 

     -- match a single character 
     matchExact :: Char -> NonEmptyContent -> Maybe Content 
     matchExact y [email protected](x,_, _, _) = makeMaybe (x == y) $ advance content 

     -- match while pattern holds for characters 
     matchPattern :: (Char -> Bool) -> NonEmptyContent -> Maybe (String, Content) 
     matchPattern p [email protected](x,xs, _, _) = do 
      let pfx = takeWhile p (x:xs) 
       len = length pfx 
      guard (len > 0) 
      content' <- advance' len content 
      return (pfx, content') 

     matchParenOpen = matchExact '(' >=> (\c -> return (ParenOpen, c)) 
     matchParenClose = matchExact ')' >=> (\c -> return (ParenClose, c)) 
     matchPlus = matchExact '+' >=> (\c -> return (Plus, c)) 
     matchNumber = matchPattern isNumber >=> (\(s, c) -> return (Number s, c)) 
     matchVariable = matchPattern isAlpha >=> (\(s, c) -> return (Variable s, c)) 

     lexOne :: Content -> (Token, Content) 
     lexOne [email protected]([], l, c) = ((End, l, c), cur) 
     lexOne (x:xs, l, c) = let cur = (x,xs,l,c) 
            tokenMatchers = [matchParenOpen, 
                 matchParenClose, 
                 matchPlus, 
                 matchNumber, 
                 matchVariable 
                ] in 
           case msum $ map ($ cur) tokenMatchers of 
            -- if nothing could be matched, generate an error and skip the character 
            Nothing -> lexOne $ advance cur 
            -- otherwise, this is an interesting token 
            Just (t, cnt) -> ((t, l, c), cnt) 

     lexAll :: Content -> [Token] 
     lexAll ([], _, _) = [] 
     lexAll content = token:lexAll rest 
        where 
         (token, rest) = lexOne content 

main :: IO() 
main = getContents >>= putStrLn . unlines . map (\(t, l, c) -> show l ++ ":" ++ show C++ ": " ++ show t) . lexer 
+0

这似乎是好的,这也包含了luqui的建议。谢谢 :) – Shahbaz

1

即使警告的确是假阳性,你可以把它作为一个提示thta你的代码是不完全清楚,并以此作为编写更清晰的代码的机会。例如:

processAll :: [Int] -> [Int] 
processAll [] = [] 
processAll (a:as) = let (x, xs) = processOne a as in x:processAll xs 
where 
    processOne x xs = (x+1,xs) 

好处:您在外部函数中有一组规范的完整列表模式。而内部反映了至少需要一个a类型的值的事实。

望着类型,内部函数的类型现在是

a -> b -> (a,b) 

,而不是

[a] -> (a, [a]) 

显然,仅此一类型表明您以前的版本不总有人。