I have simple tuples (e.g. read from a DB) from that I do not know the number of elements nor the content. E.g.
(String, Int, Int) or (String, Float, String, Int).
I want to write a generic function that would take all sort of tuples and replace all data with the string “NIL”. If the string “NIL” is already present it should stay untouched.
Coming back to the example:
("something", 3, 4.788) should result in ("something", "NIL", "NIL")
("something else", "Hello", "NIL", (4,6)) should result in ("something else", "NIL", "NIL", "NIL")
I have obviously no idea where to start since it won’t be a problem to do this with tuples that are known. Is it possible here to come to my desired result without Template Haskell?
It’s possible using
GHC.Generics, I thought I’d document it here for completeness though I wouldn’t recommend it over the other recommendations here.The idea is to convert your tuples into something that can be pattern matched on. The typical way (which I believe
HListuses) is to convert from a n-tuple to nested tuples:(,,,)->(,(,(,))).GHC.Genericsdoes something similar by converting the tuples to nested applications of the product:*:constructor.toandfromare functions that convert a value to and from their generic representation. The tuple fields are generically represented byK1newtypes, so what we want to do is recurse down through the tree of metadata (M1) and product (:*:) nodes until we find theK1leaf nodes (the constants) and replace their contents with a “NIL” string.The
Rewritetype function describes how we’re modifying the types.Rewrite (K1 i c) = K1 i Stringstates that we’re going to replace each value (thectype parameter) with aString.Given a little test app:
We can use a generic rewriter to produce:
*Main> :main ("something","NIL","NIL") ("something else","NIL","NIL","NIL")Using the built-in
Genericsfunctionality and a typeclass to do the actual transformation:And a few instances unused by this example, they’d be required for other data types:
With a bit more effort the
Typeableconstraint could be removed from theK1instance, whether it’s better or not is arguable due to Overlapping/UndecidableInstances. GHC also can’t infer the result type, though it’s seems like it should be able to. In any case, the result type needs to be correct or you’ll get a hard to read error message.