Reputation: 563
I'm trying to read an integer from a command line prompt and I want my program keep asking for input until it can parse a proper value.
This is what I came up with
import Control.Exception
import System.IO
prompt :: String -> IO String
prompt text = do
putStr text
hFlush stdout
getLine
getInt :: IO Int
getInt = handle recoverError readParse
where recoverError :: SomeException -> IO Int
recoverError _ = getInt
readParse = fmap read $ prompt ">> "
main :: IO ()
main = fmap show getInt >>= putStrLn
I expected the handle
function to recursively call getInt
any time an Exception
from read
arises, but apparently that's not the case.
This is what I see when I execute this program
$ ./main
>> 10
10
$ ./main
>> not a number
main: Prelude.read: no parse
I'm new to haskell so probably missing something obvious here.
Any help is appreciated.
Upvotes: 1
Views: 198
Reputation: 664548
The problem is that the exception from read
does not arise from readParse
due to Haskell's laziness. The read
result is not evaluated until the show
n string is output by putStrLn
, and only then the exception happens.
You could probably work around this by using evaluate
, but the proper solution here is not to rely on exceptions at all:
read
fails with anerror
if the parse is unsuccessful, and it is therefore discouraged from being used in real applications. UsereadMaybe
orreadEither
for safe alternatives.
getInt :: IO Int
getInt = maybe getInt return =<< readMaybe <$> prompt ">> "
-- alternatively written as
getInt = do
input <- prompt ">> "
case readMaybe input of
Just v -> return v
Nothing -> getInt
Upvotes: 4
Reputation: 152837
Use readIO
instead of read
, as in:
readParse = prompt ">> " >>= readIO
The difference is that fmap read
creates an IO action that always succeeds and produces a pure result with an exception embedded in it, while readIO
creates an IO action that may throw an IO exception but whose pure value result always succeeds. You may also combine getLine
and readIO
, using readLn
instead, as in:
prompt :: Read a => String -> IO a
prompt text = do
...
readLn
Upvotes: 4