Records in Haskell
greg at gregweber.info
Fri Jan 20 19:22:35 CET 2012
2012/1/18 Simon Peyton-Jones <simonpj at microsoft.com>:
> | > Has *is* a type class. It can be used and abused like any other.
> | > Record members with the same key ought to have the same semantics; the
> | > programmer must ensure this, not just call them all "x" or the like.
> | >
> | > Weak types these are not. The selector type is well-defined. The value
> | > type is well-defined. The record type is well-defined, but of course
> | > we define a type-class to let it be polymorphic.
> I want to mention that the issue Greg raises here is tackled under "Representation hiding".
> The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way.
> The trouble with instance declarations is that they are *always* exported. No hiding.
> Under "Representation hiding" I suggest that
> * If the record selector "foo" is in scope (by any name),
> then the corresponding Has instance is in scope too
> and vice versa.
> That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances.
I am not just concerned about Has instances from modules not under my control.
I am concerned there is a new implicit rule that must be explained to
me and that I have no control over: that every record field in the
same module with a given label must have the exact same semantics as
all others with the same label.
Currently we don't have this rule. Currently we have records with the
essentialy the same field labels, but with prefixes so they can
coexist, and they need not have the same semantics.
There is an extra problem with adding this rule: it is common practice
to put types that arguably should be spread across multiple modules
into one (module often named Types). Some form of this must be done
out of necessity in the case of circular references between types that
will otherwise not resolve.
By default in Yesod, we do this out of necessity for records
representing database tables. We call the module Models and users are
free to write functions there. Now we have to explain to them that
they should not.
What use case for abstraction over record fields is too burdensome to
use a type class for? Lacking such a compelling use case we are adding
implicit complexity that can result in weak-typing for the unwary user
with no compelling benefit.
The wiki page for overloaded records suggests that users are expected
to write code like this for virtual selectors:
instance Has Shape "area" Float where
get = area
This is a leaky abstraction forcing me to quote function names at the
type level. I would rather not be writing this code - explaining this
code to someone means delving into implementation details that I don't
The overloaded records proposal is vying for "extensibility" by
supporting abstraction over record fields with the same name. For this
dubious use case that type classes already satisfies we lose
user-friendly implementation of obviously useful functionality like
virtual selectors, and also explainability.
There is something to be said for the fact that I can quickly
comprehend explanations of module-based record systems. I have read
over the Overloaded Records proposal several times now and I have the
gist of it, but I am still confused on details and need another couple
reads to look at all the details instead of glossing over them.
So far I have found some details on record implementations in four FP
languages. Every single one implements a module-like namespace for
records, one after abandoning the abstraction over fields approach.
There are differing good approaches to convenient access - I think
that is where it is appropriate for Haskell to attempt to take a
More information about the Glasgow-haskell-users