我仍然沉迷于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或一个字符串?
编辑:我想我应该注意。我确实看到了这个问题与其他一些问题之间的相似之处。我正在寻找的一部分我没有看到的是这里的结构实际在做什么,最佳实践和原因是什么。
答案 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 s1
或myToLower 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
个实例的一种过滤器。由于MonadPlus
是Maybe
的实例(与列表一样),因此这为我们提供了非常简单的代码。
或者,如果您想传递错误消息:
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