Haskell函数检查多种不同条件并在失败时返回错误消息的好方法是什么?
在Python或类似的语言中,它将是直截了当的:
if failure_1:
return "test1 failed"
if failure_2:
return "test2 failed"
...
if failure_n:
return "testn failed"
do_computation
Run Code Online (Sandbox Code Playgroud)
如果没有Haskell中任意嵌套的case/if语句,你怎么做?
编辑:某些测试条件可能需要IO,这会将任何测试结果放入IO monad中.我相信这会给许多解决方案带来麻烦.
C. *_*ann 12
所以,你被困在里面IO
,你想要检查一堆条件没有很多嵌套if
s.我希望你能通过回答的方式原谅我在Haskell中解决一般问题.
从抽象的角度考虑这需要如何表现.检查条件有以下两种结果之一:
可以递归查看检查多个条件; 每次运行"函数的其余部分"时,它都会触及下一个条件,直到达到刚返回结果的最后一步.现在,作为解决问题的第一步,让我们使用该结构将事情分开 - 所以基本上,我们希望将一堆任意条件转化为可以组合成多条件函数的片段.我们可以对这些作品的性质做出什么结论?
1)每件可以返回两种不同类型中的一种; 错误消息,或下一步的结果.
2)每个部分必须决定是否运行下一步,所以当组合步骤时,我们需要给它代表下一步作为参数的函数.
3)由于每个部分都需要进行下一步,为了保持统一的结构,我们需要一种方法将最终的无条件步骤转换为看起来与条件步骤相同的步骤.
第一个要求显然表明我们需要一个类似于Either String a
我们的结果的类型.现在我们需要一个组合函数来满足第二个要求,并且需要一个包装函数来适应第三个要求.此外,在组合步骤时,我们可能希望能够访问上一步中的数据(例如,验证两个不同的输入,然后检查它们是否相等),因此每个步骤都需要将上一步的结果作为参数.
因此,将每个步骤的类型err a
称为速记,其他函数可能具有哪些类型?
combineSteps :: err a -> (a -> err b) -> err b
wrapFinalStep :: a -> err a
Run Code Online (Sandbox Code Playgroud)
那么现在,那些类型的签名看起来很奇怪,不是吗?
这种"运行可能早期失败并带有错误消息的计算"的一般策略确实适用于monadic实现; 事实上,mtl包已经有一个.更重要的是,对于这种情况,它还有一个monad 变换器,这意味着你可以将错误monad结构添加到另一个monad - 例如IO
.
所以,我们可以只导入模块,创建一个类型的同义词来包裹IO
在一个温暖的模糊中ErrorT
,然后你走了:
import Control.Monad.Error
type EIO a = ErrorT String IO a
assert pred err = if pred then return () else throwError err
askUser prompt = do
liftIO $ putStr prompt
liftIO getLine
main :: IO (Either String ())
main = runErrorT test
test :: EIO ()
test = do
x1 <- askUser "Please enter anything but the number 5: "
assert (x1 /= "5") "Entered 5"
x2 <- askUser "Please enter a capital letter Z: "
assert (x2 == "Z") "Didn't enter Z"
x3 <- askUser "Please enter the same thing you entered for the first question: "
assert (x3 == x1) $ "Didn't enter " ++ x1
return () -- superfluous, here to make the final result more explicit
Run Code Online (Sandbox Code Playgroud)
test
正如您所期望的那样,运行的结果要么Right ()
是成功要么Left String
是失败要么String
是适当的消息; 如果assert
返回失败,则不执行以下任何操作.
为了测试操作的结果,IO
您可能会发现编写类似于assert
该函数的辅助函数最简单,而不是采用参数IO Bool
或其他方法.
还要注意使用liftIO
将IO
操作转换为值EIO
,并runErrorT
运行EIO
操作并Either String a
使用整体结果返回值.如果您想了解更多细节,可以阅读monad变形金刚.
通常,模式匹配比许多if语句更好,并且检查错误条件也不例外:
func :: [Int] -> Either String Int
func [] = Left "Empty lists are bad"
func [x]
| x < 0 = Left "Negative? Really?"
| odd x = Left "Try an even number"
func xs = Right (length xs)
Run Code Online (Sandbox Code Playgroud)
此函数返回错误消息或参数的长度.首先尝试错误情况,并且只有当它们都不匹配最后一个案例时才会执行.