Changes between Version 21 and Version 22 of Records/OverloadedRecordFields/Plan


Ignore:
Timestamp:
Jul 17, 2013 4:10:29 PM (2 years ago)
Author:
adamgundry
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Records/OverloadedRecordFields/Plan

    v21 v22  
    3535
    3636
    37 === Projections ===
    38 
    39 A record field constraints is introduced when a field is used in an expression. If all the `x`s in scope are record fields, then an occurrence of `x` has type `a { x :: b } => a -> b` instead of generating an ambiguity error. If there are any normal identifiers `x` in scope (as well as fields) then a use of `x` leads to an ambiguity error.
    40 
    41 
    4237=== Record field constraints ===
     38
     39A record field constraint is introduced when a field is used in an expression. If every `x` in scope is a record fields, then an occurrence of `x` has type `a { x :: b } => a -> b` instead of generating an ambiguity error. The overloaded `x` is translated using a typeclass, described below. If there are any normal identifiers `x` in scope (as well as fields) then a use of `x` leads to an ambiguity error.
    4340
    4441Record field constraints `r { x :: t }` are syntactic sugar for typeclass constraints `Get r "x" t`, where
     
    5148}}}
    5249
    53 Recall that `Symbol` is the kind of type-level strings. The notation extends to conjunctions:  `r {x :: tx, y :: ty}` means `(Get r "x" tx, Get r "y" ty)`. Note also that `r` and `t` might be arbitrary types, not just type variables or type constructors.  For example, `T (Maybe v) { x :: [Maybe v] }` means `(Get (T (Maybe b)) "x" [Maybe v])`.
     50Recall that `Symbol` is the kind of type-level strings. Roughly speaking, an occurrence of a field name `x` is translated into `getFld (Proxy :: Proxy "x")`. (Actually a slightly more general translation may be used, as [#Lensintegration discussed below].)
     51
     52The syntactic sugar extends to conjunctions:  `r {x :: tx, y :: ty}` means `(Get r "x" tx, Get r "y" ty)`. Note also that `r` and `t` might be arbitrary types, not just type variables or type constructors.  For example, `T (Maybe v) { x :: [Maybe v] }` means `(Get (T (Maybe b)) "x" [Maybe v])`.
    5453
    5554Instances for the `Get` typeclass and `GetResult` type family are automatically generated (for modules with `-XOverloadedRecordFields` enabled) using the record fields that are in scope. For example, the data type
     
    6766}}}
    6867
    69 The `(b ~ [a])` in the instance is important, so that we get an instance match from the first two parameters only. For example, if the constraint `Get (T c) "x" d` is encountered during type inference, the instance will match and generate the constraints `(a ~ c, b ~ d, b ~ [a])`. Moreover, the `GetResult` type family ensures that the third parameter is functionally dependent on the first two, which is needed to avoid ambiguity errors when composing overloaded fields.
     68The `(b ~ [a])` in the instance is important, so that we get an instance match from the first two parameters only. For example, if the constraint `Get (T c) "x" d` is encountered during type inference, the instance will match and generate the constraints `(a ~ c, b ~ d, b ~ [a])`. Moreover, the `GetResult` type family ensures that the third parameter is functionally dependent on the first two, which is needed to [#Troubleinparadise avoid ambiguity errors when composing overloaded fields].
    7069
    7170The reason for using a three-parameter class, rather than just two parameters and a type family, is to support the syntactic sugar. With a two-parameter class we could easily end up inferring types like the following, and it would be hard to reapply the sugar:
    7271
    7372{{{
    74 f :: (Has r "x", Has r "y", GetResult r "x" ~ Int, GetResult r "y" ~ Int) => r -> Int
     73f :: (Get r "x", Get "y", GetResult r "x" ~ Int, GetResult r "y" ~ Int) => r -> Int
    7574f r = x r + y r :: Int
    7675}}}
     
    165164
    166165
    167 === Polymorphic record update for lenses ===
    168 
    169 As noted above, supporting a polymorphic version of the existing record update syntax (in its full generality) is difficult. However, even if the existing record update syntax remains monomorphic, an additional motivation for polymorphic update comes from [http://hackage.haskell.org/package/lens lens]. If we automatically generate instances of an extra class like
    170 
    171 {{{
    172 class Set (r :: *) (x :: Symbol) (t :: *) where
    173   setFld :: r -> t -> r
    174 }}}
    175 
    176 and supply the instance (where `&x` is used for explicit type application of the `x` argument)
    177 
    178 {{{
    179 instance (Functor f, Has s x a, Set s x a, fs ~ f s, fa ~ f a) => Has (a -> fa) x (s -> fs) where
    180   getFld f r = setFld &s &x &a r <$> f (getFld &s &x &a r)
    181 }}}
    182 
    183 then every record field (for which a `Set` instance can be generated) is automagically a lens. This reduces the need for the current name-mangling Template Haskell implemented in the lens library. (Note that this instance requires explicit type application, or a proxy-based workaround, in order to supply the `x` argument.)
    184 
    185 More work is needed to identify the right way to formulate the `Set` class: type-changing update requires a slightly more general version, and there is a story for [https://github.com/ekmett/lens/issues/197 multiple update]. Higher-rank fields remain problematic.
     166=== Lens integration ===
     167
     168As noted above, supporting a polymorphic version of the existing record update syntax (in its full generality) is difficult. However, suppose we also generate instances of the following class, which permits type-changing update of single fields:
     169
     170{{{
     171type family SetResult (r :: *) (f :: Symbol) (a :: *) :: *
     172
     173class Set (r :: *) (f :: Symbol) (a :: *) where
     174  setFld :: proxy f -> r -> a -> SetResult r f a
     175}}}
     176
     177It was implied above that a field like `foo` translates into `getFld (Proxy :: Proxy "foo") :: Get r "foo" t => r -> t`, but this is not quite the whole story. Where possible, we would like fields to be usable as lenses (e.g. using the [http://hackage.haskell.org/package/lens lens] package). This requires a slightly more general translation, using
     178
     179{{{
     180field :: (Get r f t, Accessor p f) => proxy f -> p r t
     181field z = accessor z (getFld z) (setFld z)
     182}}}
     183
     184to translate `foo` to `field (Proxy :: Proxy "foo") :: (Get r "foo" t, Accessor p "foo") => p r t`. The `Accessor` class is defined thus:
     185
     186{{{
     187class Accessor (p :: * -> * -> *) (f :: Symbol) where
     188  accessor :: proxy f -> (r -> GetResult r f) ->
     189              (forall a . Set r f a => r -> a -> SetResult r f a) ->
     190              p r (GetResult r f)
     191}}}
     192
     193An instance of `Accessor p f` means that `p` may contain a getter and setter for the field `f`. In particular, we can give an instance for functions that ignores `f` and the setter completely:
     194
     195{{{
     196instance Accessor (->) f where
     197  accessor _ getter setter = getter
     198}}}
     199
     200Thus, whenever a field `foo` is used at a function type (by applying it or composing it, for example), this instance will be selected. If `z` is a proxy of type `Proxy "foo"`, then `foo` translates to `field z`, which computes to `accessor z (getFld z) (setFld z)`, and hence to `getFld z` by the `Accessor` instance for functions.
     201
     202However, `p` does not have to be the function arrow. Suppose the `lens` library defined the following newtype wrapper:
     203
     204{{{
     205newtype WrapLens f r a = WrapLens
     206  { fieldLens :: forall b . Set r f b => Lens r (SetResult r f b) a b }
     207
     208instance f ~ g => Accessor (WrapLens f) g where
     209  accessor _ getter setter = WrapLens (\ w s -> setter s <$> w (getter s))
     210}}}
     211
     212Now `fieldLens foo` is a lens whenever `foo` is an overloaded record field that can be updated individually (i.e. a `Set` instance exists).
     213
     214`Set` instances are not required when using fields as functions, only when using them as more general `Accessor` instances, so if a `Set` instance cannot be generated (since the field cannot be updated without updating other fields) the basic story about projections still works.
    186215
    187216
    188217=== Trouble in paradise ===
    189218
    190 [http://www.haskell.org/pipermail/glasgow-haskell-users/2013-July/024064.html Edward Kmett points out] that the current story falls short in one important respect: composition of polymorphic record fields leads to ambiguity errors, as the intermediate type cannot be determined. For example, suppose
    191 
    192 {{{
    193 foo :: Has b "foo" c => b -> c
    194 bar :: Has a "bar" b => a -> b
     219[http://www.haskell.org/pipermail/glasgow-haskell-users/2013-July/022584.html Edward Kmett points out] that a previous version of this proposal, where the third parameter of `Get` was not functionally dependent on the first two, fell short in an important respect: composition of polymorphic record fields would lead to ambiguity errors, as the intermediate type cannot be determined. For example, suppose
     220
     221{{{
     222foo :: Get b "foo" c => b -> c
     223bar :: Get a "bar" b => a -> b
    195224}}}
    196225
     
    198227
    199228{{{
    200 foo . bar :: (Has a "bar" b, Has b "foo" c) => a -> c
    201 }}}
    202 
    203 and `b` is an ambiguous type variable.
    204 
    205 We could work around this by adding a functional dependency
    206 
    207 {{{
    208 class Has r (f :: Symbol) t | r f -> t where
    209   getFld :: r -> t
    210 }}}
    211 
    212 or using a type family
    213 
    214 {{{
    215 class Has r (f :: Symbol) where
    216   type GetResult r f :: *
    217   getFld :: r -> GetResult r f
    218 }}}
    219 
    220 but either of these options prevents the integration with lenses discussed above, and we lose support for universally quantified fields (though they are dubious anyway). Pick your poison.
    221 
    222 
    223 === User-defined `Has` instances ===
    224 
    225 Should the user be allowed to write explicit `Has` instances? For example:
    226 
    227 {{{
    228 instance ctx => Has r "x" t where
     229foo . bar :: (Get a "bar" b, Get b "foo" c) => a -> c
     230}}}
     231
     232and `b` is an ambiguous type variable. This shows the need for the `GetResult` type family.
     233
     234
     235=== User-defined `Get` instances ===
     236
     237Should the user be allowed to write explicit `Get` instances? For example:
     238
     239{{{
     240instance ctx => Get r "x" t where
    229241  getFld = blah :: r -> t
    230242}}}
    231243
    232 Even with an explicit `Has` instance as above, the name `x` will not be in scope unless a datatype has a field with name `x`. Thus it is not really useful. The previous proposal, where `(.x)` always meant "project out the `x` field", used explicit `Has` instances for virtual fields.
     244Even with an explicit `Get` instance as above, the name `x` will not be in scope unless a datatype has a field with name `x`. Thus it is not really useful. The previous proposal, where `(.x)` always meant "project out the `x` field", used explicit `Has` instances for virtual fields.
    233245
    234246
    235247=== Hiding record selectors ===
    236248
    237 Optionally, we could [wiki:Records/DeclaredOverloadedRecordFields/NoMonoRecordFields add a flag `-XNoRecordSelectorFunctions`] to suppress the record selectors. Just as `-XOverloadedRecordFields` applies to a client module, and generates `Has` instances for that module, so `-XNoRecordSelectorFunctions` in a client module would hide all the record selectors that should otherwise be in scope. The idea is that another record system could use Template Haskell to generate functions in place of selectors, and these would not clash.
     249Optionally, we could [wiki:Records/DeclaredOverloadedRecordFields/NoMonoRecordFields add a flag `-XNoRecordSelectorFunctions`] to suppress the record selectors. Just as `-XOverloadedRecordFields` applies to a client module, and generates `Get` instances for that module, so `-XNoRecordSelectorFunctions` in a client module would hide all the record selectors that should otherwise be in scope. The idea is that another record system could use Template Haskell to generate functions in place of selectors, and these would not clash.
    238250
    239251Since the selectors are hidden by clients (on import) rather than on export, fields can still be used for record update and mentioned in import and export lists, to control access to them (as discussed in the [wiki:Records/OverloadedRecordFields/Plan#Representationhiding representation hiding] section).
     
    244256An advantage of distinguishing record projections syntactically (as in `e.x`) is that `x` is always treated as a record field, regardless of what is in scope. This allows better separation of concerns, as functions that manipulate records can be defined abstractly rather than referring to particular datatypes.
    245257
    246 One workaround is to define unused types with the appropriate field names. This is slightly odd, and we might consider adding a new declaration form '''field''' `x`, which declares `x` as a record field that is always polymorphic, rather like the function declaration
     258One workaround is to define unused types with the appropriate field names. This is slightly odd, and we might consider adding a new declaration form, which declares `x` as a record field that is always polymorphic, rather like the function declaration
    247259
    248260{{{
    249261x :: r { x :: t } => r -> t
    250 x = getFld
     262x = field
    251263}}}
    252264