我正在编写一个将财务报表转换为分类账的程序。在该程序中,我有代表不同活动的类型:
data Withdrawal = Withdrawal { wTarget :: !Text, wAmount :: !Cash, wBalance :: !Cash }
data Fee = { fFee :: !Cash, fBalance :: !Cash }
-- many more
我使用这些类型,因为我有特定于事务类型的函数。
我还想编写一个活动解析器,将CSV记录转换为这些类型,因此我创建了一个activity
sum类型:
data Activity =
ActivityFee Fee
| ActivityWithdrawal Withdrawal
| -- ...
parseActivity :: CsvRecord -> Activity
那个活动
是相当样板的。必须为新的活动类型拥有一个新的活动*
构造函数有点麻烦。
这个问题有没有更惯用或者更好的设计模式?如果是C,< code>std::variant会很方便,因为添加一个新的活动类型不需要添加一个新的样板构造函数。
我考虑过类型类,但是它们的问题是它们不是封闭的,我不能通过模式匹配来创建像< code>applyActivity :: Activity -这样的函数
一种选择是不要费心定义总和类型,而是让 parseActivity
返回钱包 -
parseActivity :: CsvRecord -> Parser (Wallet -> Wallet)
您仍然需要使用一堆定义一个大的
Parser
值
除钱包之外的其他操作-
data ActivityOps = ActivityOps {
applyActivity :: Wallet -> Wallet,
debugActivity :: String
}
这仍然不如 sum 类型通用,因为它事先限制了我们可能对活动执行的操作。若要支持新操作,我们需要更改“分析器活动操作”
值。对于 sum 类型,我们只需定义一个新函数。
此解决方案的一个变体是定义一个类型类,如
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-