[英]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
,同時適用funA
和funB
, funC
需要 map 從ErrorA
和ErrorB
的所有錯誤案例到屬於ErrorC
的新案例。 這似乎是很多樣板。
第三種選擇可能是funC
包裝來自funA
和funB
的錯誤:
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
值得指出的是,錯誤的累積是使用應用組合器實現的,即liftA2
、 liftA3
和zip
。 您不能在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 版本永遠不會為user
和post
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 output
為input -> Validation HTTPError output
,從而在您的代碼庫中保持封裝性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.