How to catch (and ignore) a call to the error func

2019-04-03 23:33发布

问题:

I'm surprised I couldn't find an answer to this anywhere.

I'm writing a roguelike and I'm using the ncurses library from hackage, which is a pretty good wrapper around the ncurses library. Now ncurses has this quirk where if you try to write the bottom right character, it does so, then it tries to move the cursor to the next character, then it fails because there's nowhere to move it to. It returns an error value that you can only ignore.

My problem is that the haskell ncurses library writer dutifully checks for any errors on all calls, and when there is one, he calls: error "drawText: etc etc.".

In other languages, like c or python, to get around this you are forced to ignore the error or catch and ignore the exception, but for the life of me I can't figure out how to do it in haskell. Is the error function unrecoverable?

I will modify the library locally to not check for errors on that function if I have to, but I hate to do that. I'm also open to any workaround that would allow me to draw that last character without moving the cursor, but I don't think that is possible.

回答1:

error is supposed to be as observable as an infinite loop. You can only catch error in IO, which is like saying "yeah you can if you know magic". But from the really nice part of Haskell, pure code, it is unrecoverable, and thus it is strongly advised not to use in your code, only as much as you would ever use an infinite loop as an error code.

ncurses is being rude and making you do magic to correct it. I'd say unsafePerformIO would be warranted to clean it up. Other than that, this is largely the same as Paul's answer.

import qualified Control.Exception as Exc

{-# NOINLINE unsafeCleanup #-}
unsafeCleanup :: a -> Maybe a
unsafeCleanup x = unsafePerformIO $ Exc.catch (x `seq` return (Just x)) handler
    where
    handler exc = return Nothing  `const`  (exc :: Exc.ErrorCall)

Then wrap unsafeCleanup around any value that would evaluate to an error to turn it into a Maybe.



回答2:

You can do this using catch from Control.Exception. Note, however, that you need to be in the IO monad to do this.

import qualified Control.Exception as Exc

divide :: Float -> Float -> Float
divide x 0 = error "Division by 0."
divide x y = x / y

main :: IO ()
main = Exc.catch (print $ divide 5 0) handler
    where
        handler :: Exc.ErrorCall -> IO ()
        handler _ = putStrLn $ "You divided by 0!"