优雅的Haskell案例/错误处理



我试图更好地了解如何处理Haskell中的错误情况,并编写了一些代码来帮助我。

有没有更好的方法(更优雅、更短、更通用)来处理多个替代项(如嵌套大小写表达式)?关于该主题的任何精彩教程?

此示例的虚构类型。 这有点简化,因为大多数情况下不仅有这些嵌套的 类型但只能按顺序检索的依赖值(例如 从标准输入读取 ID,然后从 数据库)。因此,此处的嵌套应该演示一种情况,即只有在已经检查了外部值Nothing时才可用。请参阅我的新问题以获取更好的示例。

type MyType = (Maybe (Maybe Int))

目标

当 int 小于 10 时返回 int,在其他情况下(大于或 等于 10,无或只是无)返回不同的错误消息。

process Nothing ~> "error"
process (Just Nothing) ~> "error2"
process (Just (Just 20)) ~> "error3"
process (Just (Just 5)) ~> "5"

到目前为止尝试过:

朴素的实现。

患有"蠕动压痕">

process :: MyType -> String
process t = case t of
Nothing -> "error"
Just a -> case a of
Nothing -> "error2"
Just b -> if b < 10 then show b else "error3"

也许功能

使用 may 函数,这使得它更短,但也更难阅读。

process2 :: MyType -> String
process2 t = maybe "error" (a -> maybe "error2" (b -> if b < 10 then show b else "error3") a) t

模式匹配

迄今为止最好的解决方案,但在更复杂的解决方案中是不可能的 案例(请参阅上面的注释 MyType 的类型定义)。

process3 :: MyType -> String
process3 Nothing = "error"
process3 (Just Nothing) = "error2"
process3 (Just (Just a))
| a < 10 = show a
| otherwise = "error3"

可以在 https://gist.github.com/4024395 下找到带有代码的要点

>嵌套Maybes确实很混乱。

建议1:滚动自定义错误类型并使用Either

data MyError = ReadError | TooBig Int
explain :: MyError -> String
explain ReadError = "Error: the requested Int could not be found"
explain TooBig i = "Error: the supplied Int should be at most 10, but it was " ++ show i

现在使用任一混合 Ok 值(右)和错误值(左):

type MyType = Either MyError Int

现在,许多方便的函数,如either和用于Either a的Applicative和Monad实例,使编写漂亮的代码变得容易:

myAdd :: MyType -> MyType -> MyType
myAdd i1 i2 = (+) <$> i1 <*> i2

应用性很好,或者

myMult i1 i2 = do
a <- i1
b <- i2
return $ a * b

如果你更喜欢一元符号。 我们可以以程序崩溃的方式使用either

myShow :: MyType -> String
myShow = either (error.explain) show 

或者无论如何告诉我的方式:

process4 :: MyType -> String
process4 = either explain show

建议2:滚动自定义类型

data MyType' = OK Int | ReadError | TooBig Int

并使用模式匹配。这并不像我的建议 1 那么好,因为您丢失了高阶函数重用,但它比Maybe (Maybe Int)

建议 3:使用错误单体

阅读有关Control.Monad.Error的信息,并使用提供的函数或ErrorT的monad变压器。

我认为最易读的方法是您已经尝试过的maybe函数,只是通过避免 lambdas(无点样式)并使用maybe进行最里面的检查(通过将if替换为mfilter):

import Control.Monad(mfilter)
process2 :: MyType -> String
process2 =  
maybe "error"  $ 
maybe "error2" $ 
maybe "error3" 
show . mfilter (<10) . Just 

最新更新