In the following Haskell code:
data Cmd =
CmdExit |
CmdOther
deriving (Read, Show)
guiString2Cmd s =
(return (read s :: Cmd)) `catch` \(e :: SomeException) -> return CmdExit
If I do:
guiString2Cmd "CmdOther"
it all works fine. However if I do:
guiString2Cmd "some wrong string"
the code crashes instead of evaluating to CmdExit.
How can I make the code handle the exception instead of crashing?
There exists an idiom of reading inside a 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 ++ "\""
it's unsafe for the IO
monad:
> readM "CmdOther" :: IO Cmd
CmdOther
> readM "Cmd?Other" :: IO Cmd
*** Exception: user error (Failed to parse: "Cmd?Other")
because fail
throws an IOError
exception in the case of IO
, which, however, can be handled:
*Main> (readM "Cmd?Other" :: IO Cmd) `catch` const (return CmdOther)
CmdOther
And safe in the case of Maybe
monad:
> readM "CmdOther" :: Maybe Cmd
Just CmdOther
> readM "Cmd?Other" :: Maybe Cmd
Nothing
because fail
is const Nothing
in this case.
Anyway, if you want a total function guiString2Cmd
with a signature String -> Cmd
you can write it just like readM
:
guiString2Cmd :: String -> Cmd
guiString2Cmd s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = CmdExit
and then:
> guiString2Cmd "CmdOther"
CmdOther
> guiString2Cmd "Cmd?Other"
CmdExit
Slightly more generic approach.
For *
kinds:
class Failable0 t where
fail0 :: t
readG0 :: (Failable0 t, Read t) => String -> t
readG0 s | [x] <- [x | (x, "") <- reads s] = x
| otherwise = fail0
then:
instance Failable0 Cmd where
fail0 = CmdExit
For * -> *
kinds:
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 ++ "\""
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With