簡體   English   中英

如何簡潔地表示 Haskell 中的異構和類型?

[英]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.

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