假设您有一套精心设计的功能,并且了解您的设计,您可以确定功能和参数的某些组合永远不会发生。如果想要的话,那就是编译器实际上可以推断的东西。
为了清楚起见,请举个例子(不要告诉我使用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 content@(x:_, _, _) = makeMaybe (x == y) $ advance content
-- match while pattern holds for characters
matchPattern :: (Char -> Bool) -> Content -> Maybe (String, Content)
matchPattern p content@(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 cur@([], l, c) = ((End, l, c), cur)
lexOne cur@(_, 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: _ ([], _, _)
我肯定不会发生这种情况。处理这个问题的正确方法是什么?
答案 0 :(得分: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 content@(x,_, _, _) = makeMaybe (x == y) $ advance content
-- match while pattern holds for characters
matchPattern :: (Char -> Bool) -> NonEmptyContent -> Maybe (String, Content)
matchPattern p content@(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 cur@([], 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
答案 1 :(得分:1)
即使警告确实是误报,您也可以将其作为一个提示,因为您的代码并不完全清楚,并将其作为编写更清晰代码的机会。例如:
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])
显然,仅后一种类型显示您以前的版本不是全部。