20

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.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
David McHealy
  • 2,471
  • 18
  • 34
  • Hoogle for "catch" [1]. Second link down. [1] http://haskell.org/hoogle/?hoogle=catch – Thomas M. DuBuisson Nov 22 '10 at 19:13
  • Unfortunately the error in question is not in the IO monad. Well it starts in IO, then you go runCurses, which is the Curses monad, then updateWindow, which is the Update monad. Therefore I don't think paul's answer will work. But luqui's looks like it has potential and I'll be trying it when I get home. – David McHealy Nov 22 '10 at 20:24
  • looking over ncurses I am getting the feeling it isn't going to work. `drawText` does not call `error`, it delegates straight to a C function. And it returns in the `Update` monad, which is `ReaderT Window IO a` = `Window -> IO a`, so `unsafeCleanup` is only going to work if it errors out on when generating that *function*, not when running the action (unlikely). I think your options are: catch the error in IO at the top level, or open up the curses source to let you inject a more local `catch` function. (It can be done easily, just breaks encapsulation) – luqui Nov 22 '10 at 21:52
  • Thanks luqui. I guess that's what I get for using an experimental library. I didn't realize how new it was. I may contact the creator to ask if he wants a patch. Anyways, thanks a lot man. I'm going to mark your answer because it technically answers the question, even though it didn't help me in this case. – David McHealy Nov 22 '10 at 23:34

2 Answers2

19

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!"
Paul
  • 2,218
  • 1
  • 15
  • 18
  • I think that the `$` char in the putStrLn could be removed xD. But this should be the accepted answer, as it is cleaner – dani24 Sep 08 '16 at 23:14
16

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.

This is available in the spoon package if you don't want to write it yourself (and you shouldn't -- exception code can be really tricky, especially in the presence of threads).

Simon Baars
  • 1,877
  • 21
  • 38
luqui
  • 59,485
  • 12
  • 145
  • 204
  • btw,`Exc.catch (Exc.evaluate (Just $! x)) handler` would be slightly cleaner imho – hvr Mar 28 '13 at 00:00
  • Can spoon also do `a -> Either String a` instead of `a -> Maybe a` so that I could see the message of the error that was caught? Or would that be contraindicated? – Erik Kaplun Mar 06 '15 at 12:03
  • @luqui Would you mind referencing the `spoon` package in your answer so it's more visible? I initially overlooked your comment. – sjakobi Jul 16 '19 at 15:03