[英]How can I concisely represent a heterogenous sum type in Haskell?
我正在編寫一個將財務報表轉碼為分類帳的程序。 在那個程序中,我有代表不同活動的類型:
data Withdrawal = Withdrawal { wTarget :: !Text, wAmount :: !Cash, wBalance :: !Cash }
data Fee = { fFee :: !Cash, fBalance :: !Cash }
-- many more
我使用這些類型,因為我有特定於交易類型的功能。
我還想編寫一個活動解析器,將 CSV 條記錄轉換為這些類型,因此我創建了一個Activity
總和類型:
data Activity =
ActivityFee Fee
| ActivityWithdrawal Withdrawal
| -- ...
parseActivity :: CsvRecord -> Activity
該Activity
非常樣板化。 必須為新的活動類型使用新的Activity*
構造函數有點麻煩。
這個問題是否有更慣用或更好的設計模式? 如果是 C++, std::variant
會很方便,因為添加新的活動類型不需要添加新的樣板構造函數。
我已經考慮過類型類,但它們的問題是它們沒有關閉,我無法通過模式匹配來創建 function 之類的applyActivity:: Activity -> Wallet -> Wallet
。 我看到我可以將applyActivity
變成Activity
class 的 function,但問題是只有一個參數使用此模式時此解決方案才簡單明了。 如果我們有兩個 arguments 像foo:: (ClassOne a, ClassTwo b) => a -> b -> c
,那么不清楚 class foo
應該屬於哪個。
一種選擇是不費心去定義 sum 類型,而是讓parseActivity
返回Wallet -> Wallet
操作來表征活動,包裝在一些帶有Alternative
實例的Parser
類型中。
parseActivity :: CsvRecord -> Parser (Wallet -> Wallet)
您仍然需要使用一堆<|>
為每個可能的活動組成Parser
來定義一個大的Parser
值。
除了Wallet -> Wallet
之外的其他操作可以通過讓解析器返回函數記錄來支持:
data ActivityOps = ActivityOps {
applyActivity :: Wallet -> Wallet,
debugActivity :: String
}
這仍然不如 sum 類型通用,因為它預先限制了我們可能對活動執行的操作。 為了支持新操作,我們需要更改Parser ActivityOps
值。 使用 sum 類型,我們只需定義一個新的 function。
這個解決方案的一個變體是定義一個類型類,比如
class ActivityOps a where
applyActivity :: a -> Wallet -> Wallet
debugActivity :: a -> String
並讓Parser
返回某種存在主義的東西,比如:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTSyntax #-}
data Activity where
MakeActivity :: ActivityOps a => a -> Activity
這有時不受歡迎,但它的好處是能夠輕松地對已知類型的活動調用ActivityOps
方法。
可擴展的金額是一個可能的選擇。 在那種情況下,人們會寫
type Activity = Sum '[Fee, Withdrawal]
並使用match (\fee ->...) (\withdrawal ->...)
作為模式匹配的替代品。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.