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


Ignore:
Timestamp:
Jul 17, 2013 4:10:29 PM (9 months 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