【问题标题】:Defining and Catching Exceptions定义和捕获异常
【发布时间】:2014-04-03 02:25:29
【问题描述】:

我仍然掌握了 Haskell 的窍门,并且正在尝试构建我的第一个“真正的”编码项目,即我要包括测试、编写文档等的项目。我对 Haskell 足够陌生我知道我没有很多必要的语言知识,所以我想做的一切都触手可及,但这是一种想法,所以完成的动作需要我接触大部分语言的主要部分。

无论如何,我目前遇到的问题是关于在语言中抛出和捕获异常,我理解的事情可以通过多种方法来完成。我这里有个函数,toLower:

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 结构,如果它检测到异常(例如,如果有一个非字母字符,则在 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) 我如何正确设置可能引发异常或退出的函数的类型失败,否则会返回布尔值或字符串?

编辑:我想我应该注意。我确实看到了这个问题与其他一些被问到的问题之间的相似之处。我正在寻找但我没有看到的部分内容是对这里的结构实际在做什么的描述,最佳实践是什么以及为什么。

【问题讨论】:

  • 我强烈建议使用Either 而不是例外。
  • 感谢您的推荐,首先是为什么,其次,您能给我一个快速的流程指南吗?
  • 第 61 行的错误只是优先级问题。 putStrLn $ "foo" ++ "bar"(或等效的putStrLn ("foo" ++ "bar"))将起作用。
  • @JackGibbs:原因是Either 是一流的,反映在类型系统中,而异常是不可见的。 bheklilr 的回答基本上是对我的建议的充实,但使用 Maybe 而不是 Either

标签: exception haskell


【解决方案1】:

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

来自Control.Monadguard 充当MonadPlus 实例的一种过滤器。因为MaybeMonadPlus 的一个实例(和列表一样),这给了我们非常简单的代码。

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

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的类型来匹配:

doSomething :: String -> String -> Either MyError String
doSomething s1 s2 = do
    s1Lower <- myToLower s1
    s2Lower <- myToLower s2
    return $ s1Lower ++ s2Lower

如果您注意到,monadic 语法可以让我们更改函数的类型签名,甚至无需更改代码!试用此实现以了解其工作原理。

【讨论】:

  • 也感谢 Data.Char.toLower - 我可以发誓我查看了它是否提供任何此类功能。
【解决方案2】:

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

了解异常的最佳位置是 Simon Marlow 的论文 An Extensible Dynamically-Typed Heirarchy of Exceptions。他的书 Parallel Concurrent Programming in 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-30
    • 1970-01-01
    • 2011-01-31
    相关资源
    最近更新 更多