haskell - 如何在 Haskell 中编写错误类型?

标签 haskell exception error-handling

在更大的 Haskell 应用程序中,跨多个函数层聚合和处理类型错误是否存在一致的最佳实践?
来自介绍性文本和 Haskell Wiki ,我认为纯函数应该是全部的——也就是说,将错误评估为它们共同域的一部分。运行时异常无法完全避免,但应仅限于 IO 和异步计算。
如何在纯同步函数中构建错误处理?标准建议是使用 Either作为返回类型,然后为函数可能导致的错误定义代数数据类型 (ADT)。例如:

data OrderError
    = NoLineItems
    | DeliveryInPast
    | DeliveryMethodUnavailable

mkOrder :: OrderDate -> Customer -> [lineIntem] -> DeliveryInfo -> Either OrderError Order
但是,一旦我尝试将多个产生错误的函数组合在一起,每个函数都有自己的错误类型,我该如何组合错误类型?我想将所有错误汇总到应用程序的 UI 层,在那里解释错误,可能映射到特定于语言环境的错误消息,然后以统一的方式呈现给用户。当然,这个错误呈现应该不会干扰到应用的域环中的功能,应该是纯业务逻辑。
我不想定义一个 super 类型——一个包含应用程序中所有可能错误的大型 ADT;因为这意味着 (a) 所有域级代码都需要依赖于这种类型,这会破坏所有模块化,并且 (b) 这将创建对于任何给定函数来说都太大的错误类型。
或者,我可以在每个组合函数中定义一个新的错误类型,然后将单个错误映射到组合错误类型:比如 funA带有错误-ADT ErrorA , 和 funBErrorB .如果 funC , 错误类型为 ErrorC , 同时适用 funAfunB , funC需要映射 ErrorA 中的所有错误情况和 ErrorB到属于 ErrorC 的所有新案例.这似乎是很多样板。
第三种选择可能是 funC包装来自 funA 的错误和 funB :
data ErrorC
    = SomeErrorOfFunC
    | ErrorsFromFunB ErrorB
    | ErrorsFromFunA ErrorA
通过这种方式,映射变得更容易,但 UI 环中的错误处理需要了解应用程序内环中函数的确切嵌套。如果我重构域环,我确实需要触摸 UI 中的错误展开功能。
我确实找到了一个类似的 question ,但答案使用 Control.Monad.Exception似乎建议运行时异常而不是错误返回类型。问题的详细处理好像是this one由马特帕森。然而,该解决方案涉及几个 GHC 扩展、类型级编程和镜头,对于像我这样的新手来说,这需要消化很多东西,他们只是想使用 Haskell 编写一个具有适当“按书”错误处理的体面的应用程序表达类型系统。
我听说 PureScript 的可扩展记录可以更轻松地组合错误枚举。但是在 Haskell 中呢?有直接的最佳实践吗?如果是这样,我在哪里可以找到有关如何操作的文档或教程?

最佳答案

为您的聚合 Error类型,我建议你查一下validation: A data-type like Either but with an accumulating Applicative .

该库正是一个模块,仅包含少量定义。 Validation包内的类型是基本上 (虽然不是字面意思):

type Validation e a = Either (NonEmpty e) a

值得指出的是,错误的累积是使用应用组合器实现的,即liftA2。 , liftA3zip .你 不能monad 中累积错误理解,又名 do符号:
user :: Int -> Validation DomainError User
userId :: User -> Int
post :: Int -> Validation DomainError Post

userAndPost = do 
  u <- user 1
  p <- post . userId $ u
  return $ (u,p)

另一方面,应用版本可能会产生两个错误:
userAndPostA2 = liftA2 (,) (user 1) (post 1)    
userAndPost 的单子(monad)版本上面的函数永远不会为两个 user 产生两个错误和 post没有被发现。它总是一个或另一个。应用程序虽然在理论上被认为不如 monad 强大,但在某些实践中具有独特的优势。应用程序相对于 monad 的另一个优势是并发性。再一次看上面的例子,我们可以很容易地推断出为什么理解式中的 monad 可以 从不并发执行(注意帖子的获取如何取决于获取用户的用户 ID,因此规定一个操作的执行取决于另一个操作的结果)。

至于您在选择定义单个脱节联合类型时对破坏代码模块化的担忧DomainError对于所有域级别的错误,我敢说没有更好的建模方法,前提是所述特定于域的错误类型仅由域层中的函数构造和传递。一旦说 HTTP 层从域层调用该函数,它就需要将域层的错误转换为它自己的错误,例如通过类似于以下的映射函数:
eDomainToHTTP :: DomainError -> HTTPError
eDomainToHTTP InvalidCredentials = Forbidden403
eDomainToHTTP UserNotFound = NotFound404
eDomainToHTTP PostNotFound = NotFound404

使用一个这样的功能,您可以轻松地转换任何 input -> Validation DomainError outputinput -> Validation HTTPError output ,从而在您的代码库中保留封装和模块化。

关于haskell - 如何在 Haskell 中编写错误类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61851984/

相关文章:

c++ - 在 Windbg 中,当抛出特定的 C++ 异常时,我可以跳过中断吗?

haskell - 如何在 Haskell 中修改状态的一部分

haskell - Haskell 中的回溯数独

haskell - 如何让 ghci 查看我从 cabal 安装的软件包?

haskell - 尝试使用 Aeson 解码时出现莫名其妙的错误

swift - “ fatal error :在展开可选值时意外发现nil”是什么意思?

java - InternalHttpClient.getParams() 与 UnsupportedOperationException

swift - “ fatal error :在展开可选值时意外发现nil”是什么意思?

rest - 在将 ember-data 与 RESTAdapters 一起使用时,如何处理 emberJs 中的 HTTP 错误?

excel - VBA - END IF 的运行时错误