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

前端 未结 4 1181
生来不讨喜
生来不讨喜 2020-12-05 10:18

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 thi

4条回答
  •  借酒劲吻你
    2020-12-05 10:54

    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.

提交回复
热议问题