Well that function seems to glue without using actual glue, that is, just uses the Semigroup s instance as far as I can tell. So the stuff between the fields must have been present around the field Formats already.
What I mean by “glue” is: The ToJSON1 instance of [] from aeson uses the ToJSON instances of the list elements, intercalates with commas and surrounds by brackets. Here, the commas and brackets are the glue. Conversely, the parser for a list uses the list element parsers but glues them with parsers for whitespace, commas and brackets. But all this is specially crafted for the list type. How would one derive this, say, from the Rep of the recursive list data definition? Its hypothetical type signature would be:
data family Glue
tabulateWithGlue :: record f -> Glue record f -> f (record Identity)
-- example
data User f = User {name :: f String, age :: f Int}
type Glue User f = UserGlue {
glueHeader :: f (),
glueTitleName :: f (),
glueSeparator :: f (),
glueTitleAge :: f (),
glueFooter :: f ()
}
You see that the number of glue fields is not directly correlated to the number of fields in the record, but rather to the number of fields plus the syntax surrounding them. Such a record would enable the user to exercise fine control over the tabulated result, be it a serialized value or an input form.
This quite arbitrarily adds the glue to some adjacent field. I’ve written many parsers like that, but it is not principled. What if you need to import/export a different format that shares the fields but the glue is different? No code re-use. Think Yaml vs. Json, CSV separators, CRLF versus Newline, …
Not quite arbitrarily in this case, the delimiters are specified in RFCs. I see your point in general. If reuse was a concern in this case, I’d probably split the above definition in two as follows:
I think the spec reads pretty well, even if the two helper functions are a bit ugly to define:
specOptional :: Format Parser Maybe t x -> Format Parser Maybe t (Maybe x)
glueOptional :: Monoid t
=> (Format Parser Maybe t (Maybe x) -> Format Parser Maybe t (Maybe x))
-> (Format Parser Maybe t Rank2.~> Format Parser Maybe t) (Maybe x)
specOptional = mapValue Just fromJust
glueOptional f = Rank2.Arrow $ \x-> mapValue join Just $ optional (f x)
That’s the kind of glue that I was after. However, you’re still limited to having one bit of glue per field. Unlikely that this scales to more complicated and less linear data structures. In particular, for your example you had to resort to id as the glue and leave some literals in the path field.
My point is that the glue is in general not a property of an individual field, but of the field being adjacent to another field or the first/last field in the record. Speaking in terms of Generics, the glue is to be added on the :*: and Meta type constructors, not the Rec1 and K1 constructors.
I like higher kinded data. I use it (well, a close pattern) in strongweak, where it allows me to conveniently define a “strong” and “weak” version of a data type at the same time, and derive generic transformations between them:
data A (s :: Strength) = A
{ a1 :: SW s Word8
, a2 :: String }
(Lo, upon checking mid-thread, rhendric has posted a similar pattern!)
This necessitates extra boilerplate, because now you can’t derive stock X due to the SW type family. for most X. You need to write standalone derivations instead, like deriving stock instance Show A 'Strong. It’s a very minor ergonomics issue, because if you were to write this any other way via two separate definitions, you’d need to do the same anyway.
I’ve used barbies briefly, but every time so far I’ve found I was misusing it and would’ve been better off simplifying my code. For me, HKDs are the same “use with caution” bucket as promoted data constructors-- which when I learned for the first time I promptly inserted everywhere and made a big mess. Now I’ve learned to respect them and use them properly.