Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I catch read exceptions in Haskell?

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?

like image 847
mbrodersen Avatar asked May 05 '12 07:05

mbrodersen


1 Answers

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 ++ "\""
like image 166
JJJ Avatar answered Sep 22 '22 16:09

JJJ