Changes between Version 4 and Version 5 of Records/DeclaredOverloadedRecordFields

Feb 17, 2012 10:56:55 PM (6 years ago)



  • Records/DeclaredOverloadedRecordFields

    v4 v5  
    195195Some discussion threads have argued that Haskell's current record update syntax is awkward. The DORF proposal is to implement field update using a polymorphic function. Once this is implemented, alternative syntax could be explored, providing it desugars to a call to `set`.
    199 = DORF -- Implementor's view =
    201 The implementation has been prototyped in GHC 7.2.1, see
    202, and SPJ's observations/possible improvements and caveats
    205 The fact that DORF has been 'faked' in existing GHC is good evidence that it's a modest change. Furthermore, we can implement H98-style records/fields using the same mechanism.
    207 DORF is to be enabled by a compiler flag ‑XDeclaredOverloadedRecordFields, which implies flag ‑XNoMonoRecordFields, which in turn implies ‑XDisambiguateRecordFields and -XNamedFieldPuns with ‑XRecordWildCards.
    209 Note we do _not_ assume flag ‑XDotPostfixFuncApply; dot notation is not needed by DORF, it's purely syntactic sugar to suit the taste of the programmer.
    211 DORF is implemented through a class `Has' with methods `get' and `set'. (Very similar in principle to SORF.) There's an instance of `Has' for each record/field combination, with the instance generated from the record declaration.
    213 Within each instance, get/set are defined in terms of the record's data constructors, using ‑XDisambiguateRecordFields and friends.
    216 fieldLabel declaration (data dictionary)
    218 There is to be a new declaration type, examples:
    219 {{{
    220     fieldLabel customer_id :: r -> Int
    221     fieldLabel unitPrice :: (Save r, Num t) => r -> t
    222     fieldLabel monoField :: Rec -> String   -- equiv to H98
    223 }}}
    224 [`fieldLabel` is rather long as reserved words go. I'm guessing that field or label would already be heavily used in existing code. Suggestions welcome!]
    226 The fieldLabel declaration desugars to:
    227 {{{
    228     data Proxy_customer_id                  -- phantom, a type 'peg'
    229     customer_id :: r{ customer_id :: Int } => r -> Int
    230     customer_id r = get r (undefined :: Proxy_customer_id)
    232     unit_Price :: (r{ unit_Price :: t}, Save r, Num t) => r -> t
    233 }}}
    234 That is: the `r{ ... }` constraint is added by the desugarer (and will be further desugarred to a `Has` constraint):
    236 Syntactic sugar for `Has`
    238 DORF steals from SORF:
    239 {{{
    240     r{ fld :: t }  <===> Has r Proxy_fld t
    241 }}}
    242 Using the sugar in the surface syntax (representation) allows for some freedom in the design space 'behind the scenes'.
    244 Should `get` have a Proxy argument?
    245 I've used a phantom/proxy type (in GHC v 7.2.1) to drive type instancing for `Has`.
    246 SORF uses a String Kind (which is only partially available with GHC v 7.4.1), with implicit type application (so `get` does not have a proxy argument).
    247 I'll leave it to the implementors to determine which works best.
    249 `get` is a method of the `Has' class:
    250 {{{
    251     get :: (Has r fld t) => r -> fld -> t
    252 }}}
    253 Record declaration
    254 {{{
    255     data Customer_NameAddress = Cust_NA { customer_id :: Int, ... }
    256 }}}
    257 Does _not_ create a field selector function `customer_id`. Instead it creates a `Has` instance:
    258 {{{
    259     instance (t ~ Int)
    260        => Has Customer_NameAddress Proxy_customer_id t where
    261                get Cust_NA{customer_id} = customer_id
    262 }}}
    263 Note the bare `t` with type equality constraint. This is unashamedly stolen from SORF's "functional-dependency-like mechanism (but using equalities) for the result type". So type inference binds to this instance based only on the record type and field (type 'peg'), then 'improves' the type of the result.
    264 The definition of `get` uses ‑XDisambiguateRecordFields style (with ‑XNamedFieldPuns).
    266 [It's a wart that in the record declaration, we've had to repeat the type of `customer_id` when the `fieldLabel` decl has already stipulated `Int`. It is legal syntax to omit the type in the record decl, but that currently has a different effect:
    267 {{{
    268     data ... = Cust_NA { customer_id, custName :: String, ... }
    269 }}}
    270 currently means `customer_id` is to be same type as `custName`.
    272 Opportunity for improvement! ]
    274 === Record/field update ===
    275 Update uses method `set` from the `Has` class:
    276 {{{
    277         set :: (Has r fld t) => fld -> t -> r -> r
    278 }}}
    279 `set`s instances are defined using explicit data constructor:
    280     instance (t ~ String) =>
    281         Has Customer_NameAddress Proxy_firstName t where
    282             set _ x (Cust_NA{..}) = Cust_NA{firstName = x, ..}
    283 The definition of `set' uses ‑XDisambiguateRecordFields style (with ‑XNamedFieldPuns and ‑XRecordWildCards to fill in the un-named fields).
    284 Haskell's existing update syntax is desugarred to a call to `set':
    285     myCustNA{ firstName = "John" }
    286 ===>  set (undefined :: Proxy_firstName) "John" myCustNA
    287 (That is, with a name or expression preceeding the { ... }. A data constructor prefix continues to use -XDisambiguateRecordFields.)
    288 It is crucial to this proposal that we can implement a polymorphic field update function (`set'). There's a number of tricky requirements considered below.
    289 In amongst the discussion of dot notation for field selection, there have been aspersions cast on Haskell's current record update syntax.
    291 If we can support update as just a function, there's a chance we can then turn to the syntactic sugar. (For example, the application programmer can develop update idioms to suit their purpose, as just overloaded functions.)
    293 Updating multiple fields
    294 The syntax already supports updating multiple fields:
    296     myCustNA { firstName = "Fred", lastName = "Dagg" }
    297 The simplest implementation is to turn this into two (nested) updates, but that makes it inefficient generating then discarding the interim result. It may also block updates of linked fields that share a type parametric in the record type.
    298 The prototype for this proposal has explored updating with a type instance pairing the two fields:
    299     instance (t ~ (String, String)) =>
    300         Has Customer_NameAddress
    301               (Proxy_firstName, Proxy_lastName) t     where ...
    302 but in general, this would need instances for each perm/comb of fields.
    303 Type-changing update
    304 Haskell 98's record update can change the type of the record, by changing the type of a field that is parametric in the record's type.
    305 There has been some feedback that there are legitimate use-cases for type-changing update -- for example traversing a record structure applying some type-changing transformation.
    306 This proposal does support type changing, but at cost of considerable extra complexity.
    307 So the earlier definitions of Has/get/set have been "economical with the truth". Instead:
    308     class Has r fld t   where
    309         get  :: r -> fld -> GetResult r fld t
    310         set  :: (Has (SetResult r fld t) fld t) =>
    311                    fld -> t -> r -> SetResult r fld t
    312 The type functions are to handle the possibly-changing types:
    313     type family GetResult  r fld t   :: *  -- result from get
    314     type family SetResult  r fld t   :: *  -- result from set
    315 For monomorphic (non-changing) fields, GetResult returns t and SetResult returns r, so this amounts to the simpler definitions for Has/get/set given earlier.
    316 These are type families, not associated types, because in many cases, the result from get depends only on `fld', and the result from set depends only on the record type `r'. In a few cases, the type function must be sensitive to the combination of field type and record type.
    317 The extra Has constraint on set's result is to 'improve' `t' by gathering constraints from the type of set's resulting record type.
    318 Note that the field value's type `t' is the type to-be in the result, _not_ the type as-was in the record being updated.
    319 So the result from set has that type `inserted'.
    320 Example, based on field unit_Price:
    321     data Customer_Price a = Num a => Cust_Price {
    322                                        ...,
    323                                        unit_Price  :: a,
    324                                        ... }
    325     type instance GetResult (Customer_Price a) Proxy_unit_Price t
    326            = a           -- type as is
    327     type instance SetResult (Customer_Price _a) Proxy_unit_Price t
    328            = Customer_Price t      -- set record type per arg to set
    329     instance (Num t) =>
    330         Has (Customer_Price a) Proxy_unitPrice t        where
    331             get Cust_Price{unit_Price} _ = unit_Price
    332             set _ x Cust_Price{..} = Cust_Price{ unit_Price = x, .. }
    333 (The method definitions are 'uninteresting', compared to the drama to get the types right.)
    334 The extra complexity to support changing type could be somewhat reduced using a separate `Set' class with four type parameters, including both as-was and resulting record types, and equality constraints to improve them -- rather than type family SetResult.
    335 This would mean, though, that the type sugar for Has constraints would not be adequate. Since that sugar is to be visible but the instance definitions are to be 'internal', this proposal prefers to support the sugar.
    337 Selecting polymorphic/higher-ranked fields
    338 Note that initialising records with polymorphic fields (using record constructor syntax) is not affected. This proposal implements selecting/applying those fields in polymorphic contexts. This includes fields with class-constrained types 'sealed' within the record.
    339 To support higher-ranked fields, this proposal follows SORF's approach (with three parameters to Has) to obtain a polymorphic type:
    340     data HR     = HR {rev :: forall a_. [a_] -> [a_]}   -- per SPJ
    341     fieldLabel rev :: r -> (forall a_. [a_] -> [a_])
    342     ===>
    343     data Proxy_rev
    344     rev :: Has r Proxy_rev t => r -> t
    345     rev r = get r (undefined :: Proxy_rev)
    346     type instance GetResult r Proxy_rev t = t     -- plain t
    347                    -- field's type is whatever's there (it's opaque)
    348                    -- improved by the instance constraint
    349     type instance SetResult HR Proxy_rev t = HR
    350                    -- the H-R type is hidded inside HR
    351     instance (t ~ ([a_] -> [a_])) =>              -- same as SORF
    352         Has HR Proxy_rev t    where
    353             get HR{rev} _ = rev
    355 Updating polymorphic/higher-ranked fields
    356 The prototype for this proposal does include a method of updating Higher-ranked fields. SPJ has quickly reviewed the prototype:
    357 "Your trick with SetTy to support update of polymorphic fields is, I belive, an (ingenious) hack that does not scale. I think it works only for fields that are quantified over one type variable with no constraints.
    358 So, I think that update of polymorphic fields remains problematic. "
    359 Note that the "(ingenious)" and unscalable "hack" appears only in compiler-generated code.
    360 Is it a requirement to be able to update polymorphic fields? Is it sufficient to be able to update other (monomorphic) fields in records that also contain poly fields?
    362 Representation hiding/import/export
    363 See the discussion under <Application Programmer's view> and <No Mono Record Fields>. When import/exporting do we need to also export the Proxy_type? If not exported, update syntax cannot be desuggarred to use it.)
    365 Should application programmers declare instances for `Has'/set?
    366 Nothing so far suggests they should. (And there's obvious dangers in allowing it.)
    367 But updating through virtual fields might need it. See <DORF -- comparison to SORF>#<Virtual record selectors>.