簡體   English   中英

如何在 Haskell 中編寫錯誤類型?

[英]How can I compose error types in Haskell?

在較大的 Haskell 應用程序中,是否有一致的最佳實踐來聚合和處理跨多個函數層的類型錯誤?

從介紹性文本和Haskell Wiki 來看,我認為純函數應該是全部的——也就是說,將錯誤作為其共同域的一部分進行評估。 運行時異常無法完全避免,但應僅限於 IO 和異步計算。

如何在純同步函數中構建錯誤處理? 標准建議是使用Either作為返回類型,然后為 function 可能導致的錯誤定義代數數據類型 (ADT)。例如:

data OrderError
    = NoLineItems
    | DeliveryInPast
    | DeliveryMethodUnavailable

mkOrder :: OrderDate -> Customer -> [lineIntem] -> DeliveryInfo -> Either OrderError Order

但是,一旦我嘗試將多個產生錯誤的函數組合在一起,每個函數都有自己的錯誤類型,我該如何組合錯誤類型? 我想將所有錯誤匯總到應用程序的 UI 層,在那里錯誤被解釋,可能映射到特定於語言環境的錯誤消息,然后以統一的方式呈現給用戶。 當然,這個錯誤呈現不應該干擾應用程序的域環中的功能,應該是純業務邏輯。

我不想定義一個超級類型——一個包含應用程序中所有可能錯誤的大型 ADT; 因為這意味着 (a) 所有域級代碼都需要依賴於這種類型,這會破壞所有模塊化,並且 (b) 這將創建對於任何給定 function 來說太大的錯誤類型。

或者,我可以在每個組合 function 和 map 中定義一個新的錯誤類型,將單個錯誤組合為組合錯誤類型:假設funA帶有 error-ADT ErrorA ,而funB帶有ErrorB 如果funC ,錯誤類型為ErrorC ,同時適用funAfunBfunC需要 map 從ErrorAErrorB的所有錯誤案例到屬於ErrorC的新案例。 這似乎是很多樣板。

第三種選擇可能是funC包裝來自funAfunB的錯誤:

data ErrorC
    = SomeErrorOfFunC
    | ErrorsFromFunB ErrorB
    | ErrorsFromFunA ErrorA

通過這種方式,映射變得更容易,但 UI 環中的錯誤處理需要了解應用程序內環中函數的確切嵌套。 如果我重構域環,我確實需要在 UI 中觸摸展開 function 的錯誤。

我確實找到了一個類似的問題,但使用Control.Monad.Exception的答案似乎暗示了運行時異常而不是錯誤返回類型。 這個問題的詳細處理似乎是馬特·帕森(Matt Parson)的這個 然而,該解決方案涉及幾個 GHC 擴展、類型級編程和鏡頭,對於像我這樣的新手來說,這需要消化很多東西,他們只是想使用 Haskell 編寫一個具有適當“按書”錯誤處理的體面的應用程序表達類型系統。

我聽說 PureScript 的可擴展記錄可以更輕松地組合錯誤枚舉。 但是在 Haskell 中呢? 有直接的最佳實踐嗎? 如果是這樣,我在哪里可以找到有關如何操作的文檔或教程?

對於您的可聚合Error類型,我建議您查找驗證:像 Either 但具有累積 Applicative 的數據類型

該庫正是一個模塊,僅包含少量定義。 package 中的Validation類型本質上是(盡管不是字面意思):

type Validation e a = Either (NonEmpty e) a

值得指出的是,錯誤的累積是使用應用組合器實現的,即liftA2liftA3zip 不能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 function 的 monad 版本永遠不會為userpost not found 產生兩個錯誤。 它總是一個或另一個。 應用程序雖然在理論上被認為不如 monad 強大,但在某些實踐中具有獨特的優勢。 應用程序相對於 monad 的另一個優勢是並發性。 再一次看上面的例子,我們可以很容易地推斷出為什么理解中的 monad永遠不能同時執行(注意帖子的獲取如何依賴於獲取的用戶的用戶 id,因此決定一個動作的執行取決於另一個)。

至於您在選擇為所有域級別錯誤定義單個脫節聯合類型DomainError時擔心破壞代碼模塊化,我敢說沒有更好的方法來 model 它,前提是所述特定於域的錯誤類型僅由領域層中的函數構造和傳遞。 一旦說 HTTP 層從域層調用 function ,它就需要將域層的錯誤轉換為它自己的錯誤,例如通過映射 ZC1C425268E68385D1AB5074C17A94F1 的錯誤:

eDomainToHTTP :: DomainError -> HTTPError
eDomainToHTTP InvalidCredentials = Forbidden403
eDomainToHTTP UserNotFound = NotFound404
eDomainToHTTP PostNotFound = NotFound404

使用這樣的 function,您可以輕松地將任何input -> Validation DomainError outputinput -> Validation HTTPError output ,從而在您的代碼庫中保持封裝性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM