在我看来,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
这种方式以注入异常处理函数,但对我来说似乎很难看。有更好的选择吗?
您遇到的问题是因为您用纯代码引发了异常:类型throw
为Exception e => e -> a
。纯代码中的异常是不精确的,因此不能保证IO
操作的顺序。所以catch
看不到纯净的throw
。为了解决这个问题,您可以使用evaluate :: a -> IO a
,它可以“用于对其他IO操作进行评估”(来自文档)。evaluate
就像回报,但它同时要求评估。因此,您可以将替换liftM ifun getLine
为evaluate . ifun =<< getline
,ifun
以在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
使用Left
,Right
而不是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] 删除。
我来说两句