我仍然掌握着haskell,并且正在尝试构建我的第一个"真正的"编码项目,即我将要包括测试,编写文档等等.我对haskell的新知识是我知道的我没有很多必要的语言知识,所以我想做的一切都是触手可及的,但这就是这个想法,所以完成的行为需要我接触大部分的主要部分.语言.
无论如何,所以我目前的问题是关于在语言中抛出和捕获异常,我理解的东西可以通过各种不同的方法来完成.我在这里有一个功能,以下:
toLower :: String -> String
toLower plaintext =
if (catch (nonAlpha plaintext) handlerNonAlpha)
then map charToLower plaintext
else exitFailure
Run Code Online (Sandbox Code Playgroud)
如果字符串包含任何非字母字符(如果不是AZ或az),或者如果不将字符串转换为小写,则将获取字符串,抛出异常并退出.所以我对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
Run Code Online (Sandbox Code Playgroud)
正如我所说,我对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
Run Code Online (Sandbox Code Playgroud)
所以我想我的两个问题是,a)处理程序的类型出现了什么问题(第61行错误),以及b)如何正确设置可能引发异常或退出失败的函数的类型,但是否则会返回一个bool或一个字符串?
编辑:我想我应该注意.我确实看到了这个问题与其他一些问题之间的相似之处.我正在寻找的一部分我没有看到的是对这里的结构实际在做什么的描述,以及最佳实践和原因.
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
Run Code Online (Sandbox Code Playgroud)
这个代码不仅更清晰,而且现在我们根本不用担心错误处理,而其他人使用你的代码也不会产生令人讨厌的惊喜.相反,失败的概念是在类型级别编码的.要将其用作"错误处理"机制,只需在Maybe monad中工作:
doSomething :: String -> String -> Maybe String
doSomething s1 s2 = do
s1Lower <- myToLower s1
s2Lower <- myToLower s2
return $ s1Lower ++ s2Lower
Run Code Online (Sandbox Code Playgroud)
如果任一myToLower s1或myToLower s2退货Nothing,然后doSomething将返回Nothing.没有歧义,没有机会处理未处理的异常,并且在运行时没有崩溃.Haskell异常本身,由函数抛出的异常throw必须被捕获,必须catch在IOmonad中执行.没有IOmonad,你就无法捕捉异常.在纯函数中,您始终可以使用其他数据类型来表示失败的概念,而无需求助throw,因此不需要使用它过度复杂化代码.
另外,你甚至可以myToLower单独写作
import Control.Monad
-- Other code
myToLower :: String -> Maybe String
myToLower plaintext = do
guard $ not $ hasNonAlpha plaintext
return $ map toLower plaintext
Run Code Online (Sandbox Code Playgroud)
在guard从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
Run Code Online (Sandbox Code Playgroud)
然后你可以改变doSomething匹配的类型:
doSomething :: String -> String -> Either MyError String
doSomething s1 s2 = do
s1Lower <- myToLower s1
s2Lower <- myToLower s2
return $ s1Lower ++ s2Lower
Run Code Online (Sandbox Code Playgroud)
如果您注意到,monadic语法允许我们更改函数的类型签名,甚至无需更改代码!玩这个实现,以了解它的工作原理.