It is a interesting idea to create a record of parsers and “traverse it” to get a parser of records.
I’ve also been parsing bank statements (from different bank) but in that case, instead of trying
to parse different formats to one record type I found it much easier to have one data type per format, use automatic parsing instances and then convert each format to a final (unified) data type.
So for example, let’s say we have two similar , one with a name
and date
and the other with title
and day
I would have 3 types
data NameAndDate = NameAndDate
{ name :: Text
, date :: Day
} -- deriving Cassava
data TitleAndDay = TitleAndDay
{ title :: Text
, day :: Day
} -- deriving Cassava
and the final type (the one to work with)
data Transaction = Transaction
{ tranName :: Text
, tranDate :: Day
}
and some trivial converters
nameAndDayToTransaction x = Transaction (name x) (day x)
titleAndDateToTransaction x = Transaction (title x) (date y)
The problem with this approach is that you can only derive the Cassava or Aeson instance automatically if the field name to parse are valid Haskell identifier, which is pretty much never the case. In the case of NameAndDay
you’ll parse a csv with columns name
and day
, but you can’t have Name
or transaction name
etc …
Ideally we would need an easy to specify an external field name. This could be done via type annotation or maybe an external typeclass.
The idea of “traversing” a record is not new and can be generalized to any Applicative.
Some “record” packages like vinyl provide rtraverse. Barbie might provide an equivalent as well.
I rolled up my own package (metamorphosis) for that and use it to traverse the result of a parsing itself (as in MyRecord Result -> Result (MyRecord Identity)
but not with a parser itself, which seems a good idea.