在下面的Haskell代码中:
data Cmd =
CmdExit |
CmdOther
deriving (Read, Show)
guiString2Cmd s =
(return (read s :: Cmd)) `catch` \(e :: SomeException) -> return CmdExit
如果我这样做:
guiString2Cmd "CmdOther"
一切正常。但是,如果我这样做:
guiString2Cmd "some wrong string"
代码崩溃而不是评估为CmdExit。
如何让代码处理异常而不是崩溃?
答案 0 :(得分:13)
使用reads
函数(总计),并将失败案例包装为Maybe
,如下所示:
maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(x, "")] -> Just x _ -> Nothing
maybeRead
是一种非常通用的安全解析方式。
答案 1 :(得分:2)
解决方案是简单地使用读取。
答案 2 :(得分:2)
我个人建议使用safe
包中的readMay
:
readMay :: Read a => String -> Maybe a
然后你可以在'Maybe a'结果上进行模式匹配,使用'maybe',或者甚至使用'Maybe'monad来处理结果。
答案 3 :(得分:1)
在monad中存在一种阅读的习语:
readM :: (Monad m, Read a) => String -> m a
readM s | [x] <- [x | (x, "") <- reads s] = return x
-- or @[x] <- [x | (x, _) <- reads s] = return x@
-- to allow the garbage at the end of parsed string
| otherwise = fail $ "Failed to parse: \"" ++ s ++ "\""
IO
monad不安全:
> readM "CmdOther" :: IO Cmd
CmdOther
> readM "Cmd?Other" :: IO Cmd
*** Exception: user error (Failed to parse: "Cmd?Other")
因为fail
在IOError
的情况下会抛出IO
例外,但是可以处理:
*Main> (readM "Cmd?Other" :: IO Cmd) `catch` const (return CmdOther)
CmdOther
在Maybe
monad:
> readM "CmdOther" :: Maybe Cmd
Just CmdOther
> readM "Cmd?Other" :: Maybe Cmd
Nothing
因为在这种情况下fail
是const Nothing
。
无论如何,如果您想要一个带有签名guiString2Cmd
的总函数String -> Cmd
,您可以像readM
一样编写它:
guiString2Cmd :: String -> Cmd
guiString2Cmd s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = CmdExit
然后:
> guiString2Cmd "CmdOther"
CmdOther
> guiString2Cmd "Cmd?Other"
CmdExit
稍微更通用的方法。
适用于*
种:
class Failable0 t where
fail0 :: t
readG0 :: (Failable0 t, Read t) => String -> t
readG0 s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = fail0
然后:
instance Failable0 Cmd where
fail0 = CmdExit
适用于* -> *
种:
class Failable f where
fail :: String -> f a
class Functor f => Pointed f where
pure :: a -> f a
readG :: (Failable f, Pointed f, Read a) => String -> f a
readG s | [x] <- [x | (x, "") <- reads s] = pure x
| otherwise = fail $ "Failed to parse: \"" ++ s ++ "\""