提问者:小点点

如何在Haskell中简洁地表示异构和类型?


我正在编写一个将财务报表转换为分类账的程序。在该程序中,我有代表不同活动的类型:

data Withdrawal = Withdrawal { wTarget :: !Text, wAmount :: !Cash, wBalance :: !Cash }
data Fee = { fFee :: !Cash, fBalance :: !Cash }
-- many more

我使用这些类型,因为我有特定于事务类型的函数。

我还想编写一个活动解析器,将CSV记录转换为这些类型,因此我创建了一个activitysum类型:

data Activity =
  ActivityFee Fee
  | ActivityWithdrawal Withdrawal
  | -- ...

parseActivity :: CsvRecord -> Activity

那个活动是相当样板的。必须为新的活动类型拥有一个新的活动*构造函数有点麻烦。

这个问题有没有更惯用或者更好的设计模式?如果是C,< code>std::variant会很方便,因为添加一个新的活动类型不需要添加一个新的样板构造函数。

我考虑过类型类,但是它们的问题是它们不是封闭的,我不能通过模式匹配来创建像< code>applyActivity :: Activity -这样的函数


共2个答案

匿名用户

一种选择是不要费心定义总和类型,而是让 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-