定义和捕获例外

时间:2014-04-01 19:23:36

标签: exception haskell

我仍然沉迷于haskell,并且正在尝试构建我的第一个“真正的”编码项目,即我要包括测试,编写文档等等。我对haskell很新在那里我知道我没有很多必要的语言知识,所以我想做的一切都是触手可及的,但这就是这个想法,所以完成的行为需要我接触大部分的主要的语言部分。

无论如何,所以我当前遇到的问题是关于在语言中抛出和捕获异常,我理解的东西可以通过各种不同的方法来完成。我在这里有一个功能,下载:

toLower :: String -> String
toLower plaintext =
  if (catch (nonAlpha plaintext) handlerNonAlpha)
  then map charToLower plaintext
  else exitFailure

如果字符串包含任何非字母字符(如果不是A-Z或a-z),或者如果不将字符串转换为小写,则将获取字符串,抛出异常并退出。所以我对nonAlpha函数的作用是:

--- detect non-alpha character - throw error if existant
data NonNumericException = NonNumException

instance Exception NonNumericException

handlerNonAlpha :: NonNumericException -> IO()
handlerNonAlpha ex =
  putStrLn "Caught Exception: " ++ (show ex) ++ " - A non-alpha character was included in the plaintext."

nonAlpha :: String -> Bool
nonAlpha str =
  let nonalphas = [x | x <- str, (ord x) < 65 || (90 < (ord x) && (ord x) < 97) || 123 < (ord x)]
  in if (length nonalphas) == 0
     then True
     else throw NonNumException

正如我所说,我对haskell很新,所以我对这个数据/实例结构的工作原理有点模糊,但据我所知,我正在定义一个父NonNumericException,其中NonNumException是一个子(我可以有更多),并在实例行中将它们定义为异常。 catch结构,如果它检测到异常(例如,如果存在非alpha字符,则在nonAlpha的末尾抛出一个异常),然后调用处理程序。

所以这是我得到的编译错误:

utilities.hs:61:3:
Couldn't match expected type `[Char]' with actual type `IO ()'
In the return type of a call of `putStrLn'
In the first argument of `(++)', namely
  `putStrLn "Caught Exception: "'
In the expression:
  putStrLn "Caught Exception: "
  ++
    (show ex)
    ++ " - A non-alpha character was included in the plaintext."

utilities.hs:61:3:
Couldn't match expected type `IO ()' with actual type `[Char]'
In the expression:
  putStrLn "Caught Exception: "
  ++
    (show ex)
    ++ " - A non-alpha character was included in the plaintext."
In an equation for `handlerNonAlpha':
    handlerNonAlpha ex
      = putStrLn "Caught Exception: "
        ++
          (show ex)
          ++ " - A non-alpha character was included in the plaintext."

utilities.hs:73:7:
Couldn't match expected type `Bool' with actual type `IO ()'
In the return type of a call of `catch'
In the expression: (catch (nonAlpha plaintext) handlerNonAlpha)
In the expression:
  if (catch (nonAlpha plaintext) handlerNonAlpha) then
      map charToLower plaintext
  else
      exitFailure

utilities.hs:73:14:
Couldn't match expected type `IO ()' with actual type `Bool'
In the return type of a call of `nonAlpha'
In the first argument of `catch', namely `(nonAlpha plaintext)'
In the expression: (catch (nonAlpha plaintext) handlerNonAlpha)

utilities.hs:75:8:
Couldn't match type `IO a0' with `[Char]'
Expected type: String
  Actual type: IO a0
In the expression: exitFailure
In the expression:
  if (catch (nonAlpha plaintext) handlerNonAlpha) then
      map charToLower plaintext
  else
      exitFailure
In an equation for `toLower':
    toLower plaintext
      = if (catch (nonAlpha plaintext) handlerNonAlpha) then
            map charToLower plaintext
        else
            exitFailure

所以我想我的两个问题是,a)处理程序的类型出现了什么问题(第61行错误),以及b)如何正确设置可能引发异常或退出的函数的类型失败,但否则会返回一个bool或一个字符串?

编辑:我想我应该注意。我确实看到了这个问题与其他一些问题之间的相似之处。我正在寻找的一部分我没有看到的是这里的结构实际在做什么,最佳实践和原因是什么。

2 个答案:

答案 0 :(得分:5)

Haskell的最佳实践是利用其类型系统的强大功能,以避免需要为纯函数抛出/捕获异常。在某些情况下,抛出异常实际上是有意义的,但对于像toLower函数这样的东西,您可以选择使用不同的返回类型。例如:

-- We can factor out our check for a non-alpha character
isNonAlpha :: Char -> Bool
isNonAlpha c = c' < 65 || (90 < c' && c' < 97) || 123 < c'
    where c' = ord c

-- Why throw an exception? Just return False
hasNonAlpha :: String -> Bool
hasNonAlpha str = any isNonAlpha str

-- Renamed to not conflict with Data.Char.toLower
myToLower :: String -> Maybe String
myToLower plaintext =
    if hasNonAlpha plaintext
        then Nothing
        else Just $ map toLower plaintext

这不仅是更干净的代码,而且现在我们根本不用担心错误处理,而其他人使用你的代码也不会产生令人讨厌的惊喜。相反,失败的概念是在类型级别编码的。要将其用作“错误处理”机制,只需使用Maybe monad:

即可
doSomething :: String -> String -> Maybe String
doSomething s1 s2 = do
    s1Lower <- myToLower s1
    s2Lower <- myToLower s2
    return $ s1Lower ++ s2Lower

如果myToLower s1myToLower s2返回Nothing,则doSomething将返回Nothing。没有歧义,没有机会处理未处理的异常,并且在运行时没有崩溃。 Haskell异常本身,由函数throw抛出的异常必须由catch捕获,它必须在IO monad中执行。如果没有IO monad,则无法捕获异常。在纯函数中,您可以始终用另一种数据类型表示失败的概念,而不必诉诸throw,因此不需要使用它来使代码过度复杂。


另外,您甚至可以将myToLower单独写为

import Control.Monad

-- Other code

myToLower :: String -> Maybe String
myToLower plaintext = do
    guard $ not $ hasNonAlpha plaintext
    return $ map toLower plaintext

来自guard的{​​{1}}充当Control.Monad个实例的一种过滤器。由于MonadPlusMaybe的实例(与列表一样),因此这为我们提供了非常简单的代码。

或者,如果您想传递错误消息:

MonadPlus

然后,您可以更改type MyError = String myToLower :: String -> Either MyError String myToLower plaintext = if hasNonAlpha plaintext then Left $ "The string " ++ plaintext ++ " has non-alpha character(s)" else Right $ map toLower plaintext 的匹配类型:

doSomething

如果您注意到,monadic语法允许我们更改函数的类型签名,甚至无需更改代码!玩这个实现来了解它的工作原理。

答案 1 :(得分:1)

了解异常非常有用,它们非常适合处理异常情况。

阅读关于例外的最佳地点是Simon Marlow的论文,An Extensible Dynamically-Typed Heirarchy of Exceptions。他的书“Haskell中的并行并发编程”是另一个很好用的资源。

以下是对您的问题的一些评论。

第61行的

错误

handlerNonAlpha :: NonNumericException -> IO()
handlerNonAlpha ex =
  putStrLn "Caught Exception: " ++ (show ex) ++ ...

函数参数在haskell中急切消耗。在调用putStrLn之前,您必须按如下方式修改此行以执行字符串连接:

putStrLn $ "Caught Exception: " ++ (show ex) ++ ...

nonAlpha

的评论

异常只能从IO计算中捕获,并且最好避免将它们从纯函数中抛出。除此之外,nonAlpha的问题在于它声称返回Bool,但实际上返回True或抛出异常。为什么不返回False

nonAlpha :: String -> Bool
nonAlpha str =
  let nonalphas = [x | x <- str, (ord x) < 65 || (90 < (ord x) && (ord x) < 97) || 123 < (ord x)]
  in if (length nonalphas) == 0
     then True
     else False

从这样的nonAlpha中提取您的异常抛出代码。此函数的名称及其缺少返回值表示它可能抛出异常:

trapInvalid :: String -> IO ()
trapInvalid str = unless (nonAlpha str) $ throw NonNumException