|
|
|
|
|
This is a plan to implement overloaded record fields, along the lines of SPJ's [Simple Overloaded Record Fields](records/overloaded-record-fields) proposal, as a Google Summer of Code project. (See the [ original GSoC proposal](http://www.google-melange.com/gsoc/proposal/review/google/gsoc2013/adamgundry/1), for reference.) The page on [Records](records) gives the motivation and many options. In particular, the proposal for [Declared Overloaded Record Fields](records/declared-overloaded-record-fields) is closely related but makes some different design decisions.
|
|
|
|
|
|
## Introduction
|
|
|
## Design
|
|
|
|
|
|
**SLPJ** this section should be a careful design specfication.
|
|
|
|
|
|
Motivating example:
|
|
|
### Motivating example
|
|
|
|
|
|
```wiki
|
|
|
{-# LANGUAGE OverloadedRecordFields #-}
|
... | ... | @@ -16,9 +17,33 @@ getPersonId :: r { personId :: Int } => r -> Int |
|
|
getPersonId x = x.>personId
|
|
|
```
|
|
|
|
|
|
### Record field constraints
|
|
|
|
|
|
|
|
|
If the flag `-XOverloadedRecordFields` is enabled, a new form of 'record field' constraints `r { x :: t } :: Constraint` are available where `r` and `t` are types, while `x` is a literal string. These are not typeclass constraints and do not have instances as such (though see the discussion below about virtual record fields). They can appear in contexts in the usual way (that is, they are a new primitive, like equality constraints or implicit parameter constraints). Multiple fields may be written comma-separated in a single constraint as in `r { x :: t, y :: u }`.
|
|
|
|
|
|
**SLPJ note**. I think it's more helpful to introduce it as a type-class constraint that happens to have convenient concrete syntax:
|
|
|
|
|
|
```wiki
|
|
|
class Has (r::*) (s::Symbol) (t::*) where
|
|
|
getFld :: r -> t
|
|
|
|
|
|
-- data T a = MkT { x :: [a] }
|
|
|
instance (b ~ [a]) => Has (T a) "r" b where
|
|
|
getFld (MkT { x = x }) = x
|
|
|
```
|
|
|
|
|
|
|
|
|
The `(b ~ [a])` in the instance is important, so that we get an instance match from the first two fields only.
|
|
|
|
|
|
|
|
|
Then we have convenient syntax for `(Has r "x" t)`, namely `r { x::t }`. Moreover, we get convenient syntax for conjunctions: `(Has r "x" tx, Has r "y" ty)` has shorthand `r { x::tx, y:: ty }`.
|
|
|
|
|
|
|
|
|
Don't forget that the `r` might be an arbitrary type not just a type variable or type constructor. For example, `(Has (T (Maybe b)) "x" [Maybe v])` is a perfectly fine (and soluble) constraint. I suppose that its shorthand looks like `T (Maybe v) { x :: [Maybe v] }`**End of SLPJ**
|
|
|
|
|
|
### Record field projections
|
|
|
|
|
|
|
|
|
Record field constraints are introduced by projections, which are written using a new left-associative infix operator `(.>)`. That is, if `e :: r` then `e.>x :: r { x :: t } => t`. This operator must always be applied to (at least) its second argument, so `(.>)` is invalid but `(.>x) :: forall a b . a { x :: b } => a -> b`.
|
|
|
|
... | ... | @@ -26,18 +51,25 @@ Record field constraints are introduced by projections, which are written using |
|
|
A constraint `R { x :: t }` is solved if `R` is a datatype that has a field `x` of type `t` in scope. (More precisely, if `R` contains `x :: s` then `t` must be an instance of `s`.) An error is generated if `R` has no field called `x`, it has the wrong type, or the field is not in scope. Otherwise, the new constraints are handled by the solver just like other types of constraint.
|
|
|
|
|
|
|
|
|
If multiple constructors for a single datatype use the same field name, all occurrences must have exactly the same type, as at present.
|
|
|
|
|
|
### Record selectors
|
|
|
|
|
|
|
|
|
Optionally, we could [add a flag \`-XNoMonoRecordFields\`](records/declared-overloaded-record-fields/no-mono-record-fields) to disable the generation of the usual monomorphic record field selector functions. This is not essential, but would free up the namespace for other record systems (e.g. **data-lens**). Note that `-XOverloadedRecordFields` will generate monomorphic selectors by default for backwards compatibility reasons, but they will not be usable if multiple selectors with the same name are in scope.
|
|
|
|
|
|
|
|
|
When either flag is enabled, the same field label may be declared repeatedly in a single module (or a label may be declared when a function of that name is already in scope).
|
|
|
|
|
|
### Representation hiding
|
|
|
|
|
|
If multiple constructors for a single datatype use the same field name, all occurrences must have exactly the same type, as at present.
|
|
|
|
|
|
### Representation hiding
|
|
|
Since the new constraints are **not** typeclass constraints, it is reasonable for the constraint solver to consult the fields in scope when deciding whether a solution is valid.
|
|
|
|
|
|
**SLPJ** As above, I'd like to say that they are just type-class constraints with special syntax. However, maybe their instances (unlike most type-class instances) can be limited in scope; the instance is in scope iff the record field selector function is. (Um; this sentence doesn't make so much sense if we suppress the record field selectors.) **End of SLPJ**
|
|
|
|
|
|
|
|
|
Since the new constraints are **not** typeclass constraints, it is reasonable for the constraint solver to consult the fields in scope when deciding whether a solution is valid. This enables representation hiding: exporting the field selector is a proxy for permitting access to the field. For example, consider the following module:
|
|
|
This enables representation hiding: exporting the field selector is a proxy for permitting access to the field. For example, consider the following module:
|
|
|
|
|
|
```wiki
|
|
|
module M ( R(x) ) where
|
... | ... | @@ -68,6 +100,36 @@ R { e | x = t } |
|
|
|
|
|
means the same as `e { x = t }` except that the type is determined from the data constructor `R`, rather than the field name `x`. Thus it can be used where the latter is ambiguous.
|
|
|
|
|
|
**SLPJ. Not the *data* constructor, for sure. Possibly the type constructor. But in any case, this is mean to be a disambiguation of
|
|
|
**
|
|
|
|
|
|
```wiki
|
|
|
e { x = t }
|
|
|
```
|
|
|
|
|
|
|
|
|
so surely it should look like
|
|
|
|
|
|
```wiki
|
|
|
e {T| x = t }
|
|
|
```
|
|
|
|
|
|
|
|
|
where `T` is the type constructor.
|
|
|
|
|
|
|
|
|
And there's a design choice here too. Rather than special syntax we could resolve the ambiguity if you put a type signature in one of these two places:
|
|
|
|
|
|
```wiki
|
|
|
e :: T Int { x = t }
|
|
|
or
|
|
|
e { x = t } :: T Int
|
|
|
```
|
|
|
|
|
|
|
|
|
That's less invasive syntactially, and still does the job.
|
|
|
**End of SLPJ**
|
|
|
|
|
|
## Design choices
|
|
|
|
|
|
### The projection operator
|
... | ... | @@ -75,6 +137,9 @@ means the same as `e { x = t }` except that the type is determined from the data |
|
|
|
|
|
As currently drafted, this proposal advocates using a new operator `.>` rather than changing the meaning of the dot operator, for reasons of backward compatibility and avoidance of a whole host of tricky parsing issues. This could change, if it is felt that the benefits of mimicking other languages outweigh the drawbacks of breaking backwards compatibility.
|
|
|
|
|
|
**SLPJ**. I don't agree here. Dot-notation is so convenient and so universal that I think we should use it. And there isn't any ambiguity. Dot notation is already space-aware: `M.x` is a qualified name whereas `M . x` is the composition of a data constructor `M` with a function `x`. Similarly `r.x` can mean record selection, distinct from `r . x`.
|
|
|
**End of SLPJ**.
|
|
|
|
|
|
### Virtual record fields
|
|
|
|
|
|
|
... | ... | @@ -122,6 +187,8 @@ However, evidence is supplied by the constraint solver, taking into account the |
|
|
|
|
|
### Example of constraint solving
|
|
|
|
|
|
**SLPJ** Making the first example rely on the monomorphism restriction is not a good plan!
|
|
|
|
|
|
|
|
|
Consider the example
|
|
|
|
... | ... | |