I'm writing a program that transcodes financial statements into a ledger. In that program I have types representing different activities:
data Withdrawal = Withdrawal { wTarget :: !Text, wAmount :: !Cash, wBalance :: !Cash }
data Fee = { fFee :: !Cash, fBalance :: !Cash }
-- many more
I use those types, because I have functions that are transaction-type specific.
I also wanted to write an activity parser that translates CSV records into those types, so I created an Activity
sum type:
data Activity =
ActivityFee Fee
| ActivityWithdrawal Withdrawal
| -- ...
parseActivity :: CsvRecord -> Activity
That Activity
is quite boilerplate'y. Having to have a new Activity*
constructor for a new activity type is slightly cumbersome.
Is there a more idiomatic or better design pattern for this problem? Was it C++, std::variant
would be convenient, because adding a new activity type wouldn't entail adding a new boilerplate constructor.
I've considered type-classes, but the problem with them is that they are not closed and I can't pattern match to create a function like applyActivity :: Activity -> Wallet -> Wallet
. I see that I could make applyActivity
into a function of an Activity
class, but then problem is that this solution is only straightforward if only one argument is using this pattern. If we had two arguments like foo :: (ClassOne a, ClassTwo b) => a -> b -> c
, then it's not clear to which class foo
should belong.