How to catch a no parse exception from the read function in Haskell?

10,818

Solution 1

You don't want to. You want to use reads instead, possibly like that:

maybeRead = fmap fst . listToMaybe . reads

(though you might want to error out if the second element of the tuple is not "", that is, if there's a remaining string, too)

The reason why you want to use reads instead of catching error exceptions is that exceptions in pure code are evil, because it's very easy to attempt to catch them in the wrong place: Note that they only fly when they are forced, not before. Locating where that is can be a non-trivial exercise. That's (one of the reasons) why Haskell programmers like to keep their code total, that is, terminating and exception-free.

You might want to have a look at a proper parsing framework (e.g. parsec) and haskeline, too.

Solution 2

There are readMaybe and readEither that satisfy your expectation. You find this functions in Text.Read package.

Solution 3

This is an addendum to @barsoap's answer more than anything else.

Haskell exceptions may be thrown anywhere, including in pure code, but they may only be caught from within the IO monad. In order to catch exceptions thrown by pure code, you need to use a catch or try on the IO statement that would force the pure code to be evaluated.

str2Int :: String -> Int -- shortcut so I don't need to add type annotations everywhere
str2Int = read

main = do
  print (str2Int "3") -- ok
  -- print (str2Int "a") -- raises exception
  eVal <- try (print (str2Int "a")) :: IO (Either SomeException ())
  case eVal of
    Left e -> do -- couldn't parse input, try again
    Right n -> do -- could parse the number, go ahead

You should use something more specific than SomeException because that will catch anything. In the above code, the try will return a Left exception if read can't parse the string, but it will also return a Left exception if there's an IO error when trying to print the value, or any number of other things that could possibly go wrong (out of memory, etc.).

Now, here's why exceptions from pure code are evil. What if the IO code doesn't actually force the result to be evaluated?

main2 = do
  inputStr <- getLine
  let data = [0,1,read inputStr] :: [Int]
  eVal <- try (print (head data)) :: IO (Either SomeException ())
  case eVal of
    Right () -> do -- No exception thrown, so the user entered a number ?!
    Left e   -> do -- got an exception, probably couldn't read user input

If you run this, you'll find that you always end up in the Right branch of the case statement, no matter what the user entered. This is because the IO action passed to try doesn't ever try to read the entered string. It prints the first value of the list data, which is constant, and never touches the tail of the list. So in the first branch of the case statement, the coder thinks the data is evaluated but it isn't, and read may still throw an exception.

read is meant for unserializing data, not parsing user-entered input. Use reads, or switch to a real parser combinator library. I like uu-parsinglib, but parsec, polyparse, and many others are good too. You'll very likely need the extra power before long anyway.

Solution 4

Here's an improved maybeRead which allows only for trailing whitespaces, but nothing else:

import Data.Maybe
import Data.Char

maybeRead2 :: Read a => String -> Maybe a
maybeRead2 = fmap fst . listToMaybe . filter (null . dropWhile isSpace . snd) . reads
Share:
10,818

Related videos on Youtube

davidscolgan
Author by

davidscolgan

Updated on April 28, 2022

Comments

  • davidscolgan
    davidscolgan almost 2 years

    In my Haskell program, I want to read in a value given by the user using the getLine function. I then want to use the read function to convert this value from a string to the appropriate Haskell type. How can I catch parse errors thrown by the read function and ask the user to reenter the value?

    Am I right in thinking that this is not an "IO Error" because it is not an error caused by the IO system not functioning correctly? It is a semantic error, so I can't use IO error handling mechanisms?

    • sdcvvc
      sdcvvc over 11 years
      In upcoming GHC 7.6, there will be Text.Read.readEither :: Read a => String -> Either String a and Text.Read.readMaybe :: Read a => String -> Maybe a.
  • luqui
    luqui about 13 years
    maybeRead is in the utility-ht and applicative-extras packages on hackage (among others), and it is proposed for inclusion in base. I'm not sure of the status of that proposal.
  • Ben Millwood
    Ben Millwood almost 12 years
    null . dropWhile isSpace is just all isSpace, no?
  • Titou
    Titou about 9 years
    The best answer IMHO !