【问题标题】:How to catch (and ignore) a call to the error function?如何捕获(并忽略)对错误函数的调用?
【发布时间】:2011-05-13 16:31:45
【问题描述】:

我很惊讶我无法在任何地方找到答案。

我正在编写一个 roguelike,我正在使用来自 hackage 的 ncurses 库,它是 ncurses 库的一个非常好的包装器。现在 ncurses 有这个怪癖,如果你尝试写右下角的字符,它会这样做,然后它会尝试将光标移动到下一个字符,然后它会失败,因为无处可移动。它返回一个只能忽略的错误值。

我的问题是,haskell ncurses 库编写者尽职尽责地检查所有调用中的任何错误,当有错误时,他调用:error "drawText: etc etc."。

在其他语言中,如 c 或 python,为了解决这个问题,你不得不忽略错误或捕获并忽略异常,但对于我来说,我无法弄清楚如何在 haskell 中做到这一点。误差函数不可恢复吗?

如果必须,我将在本地修改库以不检查该函数的错误,但我讨厌这样做。我也愿意接受任何允许我在不移动光标的情况下绘制最后一个字符的解决方法,但我认为这是不可能的。

【问题讨论】:

  • 胡扯“赶上”[1]。第二个链接挂了。 [1]haskell.org/hoogle/?hoogle=catch
  • 不幸的是,有问题的错误不在 IO monad 中。好吧,它从 IO 开始,然后运行 ​​runCurses,它是 Curses monad,然后是 updateWindow,它是 Update monad。因此,我认为保罗的回答不会奏效。但是luqui's看起来很有潜力,我回家后会尝试一下。
  • 查看 ncurses 我感觉它不起作用。 drawText 不调用 error,它直接委托给 C 函数。它在Update monad 中返回,即ReaderT Window IO a = Window -> IO a,所以unsafeCleanup 只有在生成该函数时出错时才会起作用,而不是在运行行动(不太可能)。我认为您的选择是:在顶层捕获 IO 中的错误,或者打开 curses 源让您注入更本地的 catch 函数。 (很容易做到,只是打破封装)
  • 谢谢卢基。我想这就是我使用实验库所得到的。我没有意识到它有多新。我可能会联系创作者询问他是否需要补丁。无论如何,非常感谢大佬。我要标记你的答案,因为它在技术上回答了这个问题,即使在这种情况下它对我没有帮助。

标签: exception haskell exception-handling referential-transparency


【解决方案1】:

error 应该像无限循环一样可观察。你只能在IO 中捕捉error,这就像在说“是的,如果你知道魔法,你就可以”。但是从 Haskell 真正好的部分,纯代码来看,它是不可恢复的,因此强烈建议不要在你的代码中使用,只要你曾经使用无限循环作为错误代码。

ncurses 是粗鲁的,让你变魔术来纠正它。我会说unsafePerformIO 有必要清理它。除此之外,这与保罗的回答基本相同。

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)

然后将unsafeCleanup 包裹在任何会评估为错误的值周围,以将其转换为Maybe

如果您不想自己编写它,可以在 spoon 包中找到它(而且您不应该 - 异常代码可能非常棘手,尤其是在存在线程的情况下)。

【讨论】:

  • btw,Exc.catch (Exc.evaluate (Just $! x)) handler 会稍微干净一些,恕我直言
  • 勺子也可以使用a -> Either String a 而不是a -> Maybe a,这样我就可以看到捕获的错误消息?还是有禁忌?
  • @luqui 你介意在你的答案中引用spoon 包,以便它更明显吗?我最初忽略了您的评论。
【解决方案2】:

您可以使用来自Control.Exceptioncatch 执行此操作。但是请注意,您需要在 IO monad 中才能执行此操作。

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!"

【讨论】:

  • 我认为 putStrLn 中的 $ 字符可以删除 xD。但这应该是公认的答案,因为它更干净
猜你喜欢
  • 2020-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
  • 2020-01-09
  • 1970-01-01
  • 2021-01-22
  • 1970-01-01
相关资源
最近更新 更多