Type-Punning Declared Overloaded Record Fields (TPDORF)

Thumbnail Sketch

This proposal is addressing the narrow issue of namespacing for record field names by allowing more than one record in the same module to share a field name. Furthermore, it is aiming at a more structured approach to higher-ranked type fields, so that they can be updated using the same surface syntax as for other fields. This actually means a less complex implementation (compared to DORF or SORF). This proposal is in the DORF 'stable', but sufficiently different it is worth making it a separate proposal.

Specifically each sharing field name is overloaded, and there is a Type with the same name (upshifted) so that:

Within the same module, many record types can be declared to share the field name.

The field name can be exported so that records in other modules can share it.

Furthermore, other modules can create records using that field name, and share it.

The export/import of both the field name and its punned Type is under usual H98 namespace and module/qualification control, so that for the record type in an importing module:

Some fields are both readable and updatable;

Some are read-only;

Some are completely hidden.

In case of 'unintended' clash (another module using the same name 'by accident'), usual H98 controls apply to protect encapsulation and representation hiding.

This proposal introduces several new elements of syntax (including some shorthands), all of which desugar to use well-established extensions of ghc. The approach is yet to be prototyped, but I expect that to be possible in ghc v 7.2.1. In particular:

The field name overloading is implemented through usual class and instance mechanisms.

Field selectors are ordinary functions named for the field (but overloaded rather than H98's monomorphic), so field selection is regular function application. (There is no need for syntactically-based disambiguation at point of use.)

Implementation: the Has class, with methods get and set, and punned Types

Record declarations generate a Has instance for each record type/field combination. There is a type argument for the record and the field.

Note that SORF introduces a third argument for the field's resulting type. (This is specifically to support higher-rank typed fields; but despite the complexity it introduces, SORF has no mechanism to update h-r fields.)

TPDORF approaches h-r fields in a different way, which supports both setting and getting those fields. (I'm not claiming this is a solution, more a well-principled work-round. And not a hack. It is both scalable, and supports class-constrained higher-rank types.)

The main insight is that to manage large-scale data models (in which namespacing becomes onerous, and name sharing would be most beneficial), there are typically strong naming conventions and representation hiding for critical fields. For example:

TPDORF makes a virtue of this punning. (So extend's H98's and NamedFieldPuns punning on the field name.) This allows for some syntactic shorthands, but still supporting H98-style declaring field names within the record decl for backwards compatibility.

Here is the Has class with instances for the above Customer record, and examples of use:

Virtual or pseudo- fields are easy to create and use, because field selection is merely function application (plus unwrapping for non-shared H98-style fields). Virtual fields look like ordinary fields (but can't be updated, because there is no Has instance):

Parametric polymorphic fields can be applied in polymorphic contexts, and can be set including changing the type of the record.(This uses the SetResult type function.)To do: provide example with desugarring.

Multiple fields can be updated in a single expression (using familiar H98 syntax), but this desugars to nested updates, which is inefficient.

Pattern matching and record creation using the data constructor prefixed to { ... } work as per H98 (using DisambiguateRecordFields and friends).

But the types are subtlely different vs. polymorphic update: you must explicitly wrap the types.(So this is not backwards compatible. Can we do this?:In Constr{ fld = e }, if e not type Fld, enwrap it with a Fld constructor.)

Higher-ranked polymorphic fields (including class-constrained) can be applied in polymorphic contexts, and can be set -- providing they are wrapped in a newtype. Here is SPJ's example: