在Haskell中捕获异常

塞文蒂米尔

在我看来,Haskell中的异常只能在引发异常后立即捕获,并且不会像Java或Python那样传播。一个简短的示例说明如下:

{-# LANGUAGE DeriveDataTypeable #-}

import System.IO
import Control.Monad
import Control.Exception
import Data.Typeable

data MyException = NoParseException String deriving (Show, Typeable)
instance Exception MyException

-- Prompt consists of two functions:
-- The first converts an output paramter to String being printed to the screen.
-- The second parses user's input.
data Prompt o i = Prompt (o -> String) (String -> i)

-- runPrompt accepts a Prompt and an output parameter. It converts the latter
-- to an output string using the first function passed in Prompt, then runs
-- getline and returns user's input parsed with the second function passed
-- in Prompt.
runPrompt :: Prompt o i -> o -> IO i
runPrompt (Prompt ofun ifun) o = do
        putStr (ofun o)
        hFlush stdout
        liftM ifun getLine

myPrompt = Prompt (const "> ") (\s -> if s == ""
    then throw $ NoParseException s
    else s)

handleEx :: MyException -> IO String
handleEx (NoParseException s) = return ("Illegal string: " ++ s)

main = catch (runPrompt myPrompt ()) handleEx >>= putStrLn

运行该程序后,当您只按[Enter]而没有键入任何内容时,我应该Illegal string:在输出中看到:而是出现了:prog: NoParseException ""现在假设Prompt类型和runPrompt函数在模块外部的公共库中定义,并且无法更改以处理传递给Prompt构造函数的函数中的异常。如何在不更改的情况下处理异常runPrompt

我曾考虑过将第三个字段添加到Prompt这种方式以注入异常处理函数,但对我来说似乎很难看。有更好的选择吗?

安塔尔·斯佩克特·扎布斯基

您遇到的问题是因为您用纯代码引发了异常:类型throwException e => e -> a纯代码中的异常是不精确的因此不能保证IO操作的顺序所以catch看不到纯净的throw为了解决这个问题,您可以使用evaluate :: a -> IO a,它可以“用于对其他IO操作进行评估”(来自文档)。evaluate就像回报,但它同时要求评估。因此,您可以将替换liftM ifun getLineevaluate . ifun =<< getlineifun以在runPrompt IO操作过程中评估其作用力(回想一下liftM f mx = return . f =<< mx,所以是相同的,但是对评估的控制更多。)并且无需更改任何其他内容,您将获得正确的答案:

*Main> :main
> 
Illegal string: 

但是,实际上,这不是我要使用异常的地方。人们在Haskell代码中并没有那么多地使用异常,特别是在纯代码中则没有。我宁愿写Prompt这样,以便将输入函数的潜在故障编码为以下类型:

data Prompt o i = Prompt (o -> String) (String -> Either MyException i)

然后,运行提示将仅返回Either

runPrompt :: Prompt o i -> o -> IO (Either MyException i)
runPrompt (Prompt ofun ifun) o = do putStr $ ofun o
                                    hFlush stdout
                                    ifun `liftM` getLine

我们将调整myPrompt使用LeftRight而不是throw

myPrompt :: Prompt a String
myPrompt = Prompt (const "> ") $ \s ->
             if null s
               then Left $ NoParseException s
               else Right s

然后我们either :: (a -> c) -> (b -> c) -> Either a b -> c用来处理异常。

handleEx :: MyException -> IO String
handleEx (NoParseException s) = return $ "Illegal string: " ++ s

main :: IO ()
main = putStrLn =<< either handleEx return =<< runPrompt myPrompt ()

(另外的,无关的注释:您会注意到我在这里进行了一些样式上的更改。我唯一要说的真正重要的是使用null s,而不是s == ""。)

如果您确实希望将旧的行为返回到顶层,则可以编写runPromptException :: Prompt o i -> o -> IO i该代码,将Left情况作为例外:

runPromptException :: Prompt o i -> o -> IO i
runPromptException p o = either throwIO return =<< runPrompt p o

我们不需要在evaluate这里使用throwIO因为我们正在使用,它用于在IO计算内部抛出精确的异常这样,您的旧main功能将可以正常工作。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章