Overloaded record fields
qdunkan at gmail.com
Thu Jun 27 23:08:43 CEST 2013
I'm reluctant to add yet another opinion, but, oh what the heck:
For me, lenses basically already solve the record problem. The only
missing thing is to integrate them better with record declaration
syntax. Having to rely on TH and then write a makeLenses splice is
just too much friction to have lenses added everywhere automatically.
TH makes compiles more fragile and slower, and worst of all introduces
a declaration order constraint in the source file. So I declare by
hand, but even at one line per lens, it's still too much friction,
because after all they don't start saving you lines until you want to
update a nested field.
In addition, I can't use unqualified names because I would want to
import only the record lenses unqualified, not the rest of the module.
So I'd have to move all the data types into individual modules,
because giant long import lists is definitely way too much friction.
But the separate module is no good, because that in turns doesn't let
you preserve invariants by restricting exports.
So if I were doing the GSoC thing, here's what I would do:
1 - Add an option to add a 'deriving (Lens)' to record declarations.
That makes the record declare lenses instead of functions. That's
already most of the problem solved, for me, because it removes the TH
2 - The next step is to allow 'deriving (ClassyLens)', which declares
lenses plus the typeclasses to allow shared names. Then when you
write 'import M (Record(..))', the (..) will import the stuff
generated by 'deriving (ClassyLens)', i.e. the class methods. Now you
can drop the record name prefixing and import unqualified to drop the
module name qualification as well.
It's still not ideal because you would have to add the unqualified
'import M (Record(..))', but is better than having to write out the
list of fields every single time. And actually I'm not sure I could
use even that, because record field names are usually the same as what
you want to name the variable, e.g.: "name = parent.name .^ person".
A hardcoded record field thing like SORF has the edge here because you
can write "name = person.parent.name" without 'name' clashing. But in
every other respect, lenses fit much better into the rest of the
language and are much more powerful for much less (i.e. none!) ad-hoc
language level complexity to support them, so to me they clearly win
at power to weight ratio.
But I don't mind always qualifying, e.g. 'name = Person.parent .
Person.name .^ person' and avoiding the whole classes and unqualified
import hassle, so just step 1 is basically problem solved (in fact,
this is what I already do, just without the automatic lens
Alas, not everyone shares my attitude towards qualification. In fact,
I'm probably in a small minority (hi Henning!). Which is sad, because
just doing #1 would be so easy! Maybe I should just go do it myself,
it's not like it would conflict with any of the other proposed
More information about the Glasgow-haskell-users