Haskell的方式来做中止多个项目的错误检查 [英] Haskell way to do error checking of multiple items with abort

查看:90
本文介绍了Haskell的方式来做中止多个项目的错误检查的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Haskell函数检查许多不同的条件并在失败时返回错误消息的好方法是什么?

在Python或类似语言中,如果failure_1:
返回test1失败
如果failure_2:$ b $会很简单:

  b返回test2失败
...
如果failure_n:
返回testn失败
do_computation

你如何在Haskell中没有任意嵌套的case / if语句的情况下做到这一点?



编辑:一些测试条件可能需要将IO测试结果放入IO monad中的IO。我相信这会在很多解决方案中产生一个问题。

因此,你被困在 IO ,如果 s你想检查一堆没有大量嵌套的条件。我希望你能通过回答,原谅我在Haskell上解决更一般的问题的方法。

抽象地考虑这需要如何表现。检查一个条件有两个结果之一:


  • 成功,在这种情况下,程序将运行函数的其余部分

  • 失败,在这种情况下,程序会丢弃该函数的其余部分并返回错误消息。


检查多个条件可以递归地查看;每次运行函数的其余部分时,它都会触发下一个条件,直到达到最后一步,然后返回结果。现在,作为解决问题的第一步,让我们使用该结构将事情分开 - 基本上,我们想要将一些任意条件分解成多个条件,我们可以将它们合并为一个多条件函数。我们可以得出关于这些作品的性质的结论吗?



1)每件作品可以返回两种不同类型中的一种;一个错误信息或下一步的结果。



2)每件作品都必须决定是否运行下一步,所以当合并步骤时我们需要给它函数代表下一步作为参数。

3)由于每件作品都期望得到下一步,为了保持统一的结构,我们需要一种方法来转换最终的,无条件进入看起来与条件步骤相同的东西。



第一个要求显然表明我们需要一个像类型的字符串或者一个字符串a 来获得我们的结果。现在我们需要一个组合函数来适应第二个要求,并且需要一个包装函数来适应第三个要求。另外,当组合步骤时,我们可能想要访问前一步骤的数据(比如验证两个不同的输入,然后检查它们是否相等),因此每个步骤都需要将前一步骤的结果作为参数。

因此,将每一步 err a 的类型称为简写,其他函数可能有哪些类型?

  combineSteps :: err a  - > (a→err b)→> err b 
wrapFinalStep :: a - > err a

现在,那些类型的签名看起来很奇怪,不是吗?



这种通常的策略是运行一个能够提前失败并带有错误信息的计算,这确实适用于一元实现;实际上 mtl包已经有一个。更重要的是,对于这种情况,它也有一个monad 转换器,这意味着您可以将错误的monad结构添加到另一个monad中 - 例如 IO



因此,我们可以导入模块,使一个类型同义词将 IO 模糊 ErrorT ,然后你离开:

  import Control.Monad。错误

类型EIO a = ErrorT字符串IO a

断言pred err = if pred然后返回()else throwError err

askUser prompt = do
liftIO $ putStr prompt
liftIO getLine
$ b main :: IO(或者String())
main = runErrorT test

test: :EIO()
test = do
x1< - askUser请输入除5之外的任何内容:
assert(x1 / =5)已输入5
x2 < - askUser请输入大写字母Z:
assert(x2 ==Z)未输入Z
x3 < - askUser请输入相同的东西你输入了第一个问题:
assert(x3 == x1)$没有输入++ x1
return() - 多余的,这里使最终结果更明确

运行 test 的结果与您所期望的一样,可以是 Right()取得成功,或 Left String 取得失败,其中 String 是适当的信息;如果 assert 返回失败,则不会执行以下任何操作。



用于测试 IO 动作,你可能会发现编写一个类似于 assert 的帮助器函数是最简单的,它的参数是 IO Bool 或其他方法。



另请注意,使用 liftIO IO 动作转换为 EIO runErrorT 运行 EIO 操作,并返回任一字符串a 值与整体结果。如果你想了解更多细节,你可以阅读 monad变形金刚


What is a good way for a Haskell function to check a number of different conditions and return an error message on a failure?

In Python or similar language, it would be straightforward:

if failure_1:
    return "test1 failed"
if failure_2:
    return "test2 failed"
...
if failure_n:
    return "testn failed"
do_computation

How do you do this without arbitrarily nested case/if statements in Haskell?

Edit: some of the test conditions may require IO which puts any test results in the IO monad. I believe this puts a kink in a number of solutions.

解决方案

So, you're stuck inside IO, and you want to check a bunch of conditions without lots of nested ifs. I hope you'll forgive me a digression onto more general problem solving in Haskell by way of answering.

Consider in abstract how this needs to behave. Checking a condition has one of two outcomes:

  • Success, in which case the program runs the rest of the function
  • Failure, in which case the program discards the rest of the function and returns the error message.

Checking multiple conditions can be viewed recursively; each time it runs "the rest of the function" it hits the next condition, until reaching the final step which just returns the result. Now, as a first step to solving the problem, let's break things apart using that structure--so basically, we want to turn a bunch of arbitrary conditions into pieces that we can compose together into a multi-conditional function. What can we conclude about the nature of these pieces?

1) Each piece can return one of two different types; an error message, or the result of the next step.

2) Each piece must decide whether to run the next step, so when combining steps we need to give it the function representing the next step as an argument.

3) Since each piece expects to be given the next step, to preserve uniform structure we need a way to convert the final, unconditional step into something that looks the same as a conditional step.

The first requirement obviously suggests we'll want a type like Either String a for our results. Now we need a combining function to fit the second requirement, and a wrapping function to fit the third. Additionally, when combining steps, we may want to have access to data from a previous step (say, validating two different inputs, then checking if they're equal), so each step will need to take the previous step's result as an argument.

So, calling the type of each step err a as a shorthand, what types might the other functions have?

combineSteps :: err a -> (a -> err b) -> err b
wrapFinalStep :: a -> err a

Well now, those type signatures look strangely familiar, don't they?

This general strategy of "run a computation that can fail early with an error message" indeed lends itself to a monadic implementation; and in fact the mtl package already has one. More importantly for this case, it also has a monad transformer, which means that you can add the error monad structure onto another monad--such as IO.

So, we can just import the module, make a type synonym to wrap IO up in a warm fuzzy ErrorT, and away you go:

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

The result of running test, as you would expect, is either Right () for success, or Left String for failure, where the String is the appropriate message; and if an assert returns failure, none of the following actions will be performed.

For testing the result of IO actions you may find it easiest to write a helper function similar to assert that instead takes an argument of IO Bool, or some other approach.

Also note the use of liftIO to convert IO actions into values in EIO, and runErrorT to run an EIO action and return the Either String a value with the overall result. You can read up on monad transformers if you want more detail.

这篇关于Haskell的方式来做中止多个项目的错误检查的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆