Changes between Version 3 and Version 4 of Records/DeclaredOverloadedRecordFields


Ignore:
Timestamp:
Feb 17, 2012 10:55:49 PM (4 years ago)
Author:
guest
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Records/DeclaredOverloadedRecordFields

    v3 v4  
    66 * ''' [wiki:Records/DeclaredOverloadedRecordFields/NoMonoRecordFields No Mono Record Fields] '''   (precursor to DORF)
    77 * ''' DORF -- Application Programmer's view '''     (this page)
    8  * ''' DORF -- Implementor's view '''
     8 * ''' [wiki:Records/DeclaredOverloadedRecordFields/ImplementorsView DORF -- Implementor's view] '''
    99 * ''' DORF -- Comparison to SORF '''
    1010 * ''' Dot as Postfix Funcion Apply '''   (optional syntactic sugar)
     
    1414
    1515
    16 == No Mono Record Fields ==
    17 
    18 This proposal is a precursor to overloaded record fields.
    19 
    20 There is to be a compiler flag '''-XNoMonoRecordFields'''. (Default value '''‑XMonoRecordFields''', to give H98 behaviour.)
    21 
    22 -XNoMonoRecordFields suppresses creating the field selector function from the field name in a record-style data declaration.
    23 
    24 Suppressing the function frees up the namespace, to be able to experiment with various record/field approaches -- including the 'cottage industry' of Template Haskell solutions.
    25 
    26 -XNoMonoRecordFields implies -XDisambiguateRecordFields -- otherwise the only way to access record fields is positionally. It also implies ‑XNamedFieldPuns and ‑XRecordWildCards to support field access and update. (IMHO, suppressing the field selector function should always have been part of -XDisambiguateRecordFields. I'm by no means the first to make that observation.)
    27 
    28 Note that the field name is still valid within the scope of a pattern match, or record update inside the {...} constructor syntax.
    29 
    30 
    31 === Import/Export and Representation hiding ===
    32 
    33 Since there is no field selector function created, it can't be exported or imported.
    34 
    35 If you say:
    36 {{{
    37 {-# OPTIONS_GHC -XNoMonoRecordFields                      #-}
    38 module M( T( x ) )       where
    39     data T = MkT { x, y :: Int }
    40 }}}
    41 then the existence of field `y` is hidden;
    42 type `T` and field label `x` are exported, but not data constructor `MkT`, so `x` is unusable.
    43 
    44 (Without the ‑XNoMonoRecordFields flag, field selector function `x` would be exported.)
    45 
    46 
    47 
    48 
    49 == Declared Overloaded Record Fields (DORF) ==
    5016== Application Programmer's view ==
    5117
     
    6733 * rather, we have one field name, and we lack the syntax/semantics for sharing it between different records.
    6834
    69 An example: let's say I have a database application with a field (meaning type) customer_id. Then it appears in records for name and address, pricing, order entry, etc. This is not a name 'clash', it's 'intended sharing'. (It really galls me to even put it that way for explanatory purposes. Really it's the **same** customer_id.)
     35An example: let's say I have a database application with a field (meaning type) `customer_id`. Then it appears in records for name and address, pricing, order entry, etc. This is not a name 'clash', it's 'intended sharing'. (It really galls me to even put it that way for explanatory purposes. Really it's the **same** `customer_id`.)
     36
    7037In data model design you'd typically go about identifying all the fields (types aka attributes) and putting them in a data dictionary. Then you'd construct your records from them. You might (possibly) put the data dictionary in a distinct module, for easy maintenance. But you'd certainly want all the customer-related records in the same module. So a data decl:
    7138{{{
    7239    data Customer_NameAddress = Cust_NA { customer_id :: Int, ... }
    7340}}}
    74 is _not_ declaring customer_id, it's _using_ (or instancing) an already-declared field for customer_id.
    75 Similarly, if I have a family of objects, all with a `reset' method, that's not umpteen methods with a 'clash' of names, it's one method with umpteen instances. (And I might create a family of record structures to describe each object, and store the `reset' method within it.)
     41is __not__ declaring `customer_id`, it's __using__ (or instancing) an already-declared field for `customer_id`.
     42Similarly, if I have a family of objects, all with a `rese`' method, that's not umpteen methods with a 'clash' of names, it's one method with umpteen instances. (And I might create a family of record structures to describe each object, and store the `reset` method within it.)
    7643
    7744What's more, the Haskell 98 field selector (auto-created from the data decl) is half-way to what we want. It's a function:
     
    8047}}}
    8148The DORF proposal generalises that signature: if you want to share a field across different records, its selector function needs to be overloaded to this type:
    82 
     49{{{
    8350    customer_id :: r{ customer_id :: Int } => r -> Int
    84 
    85 The 'r{ ... }' is syntactic sugar for the constraint meaning "record r has field customer_id at type Int".
     51}}}
     52The `r{ ... }` is syntactic sugar for the constraint meaning "record `r` has field `customer_id` at type `Int`".
    8653
    8754We need a way to declare that a name is available as an overloadable field name (roughly speaking, a class/method definition), proposed syntax:
    88 
     55{{{
    8956    fieldLabel customer_id :: r -> Int
    90 
    91 (The r{ ... } is added by the desugarer.)
    92 
    93 The `-> Int' means the field's domain (type) is Int -- it's just a type.
     57}}}
     58(The `r{ ... }` is added by the desugarer.)
     59
     60The `-> Int` means the field's domain (type) is `Int` -- it's just a type.
    9461We might also want to constrain the record -- for example to be sure it is savable to persistent storage:
    95 
     62{{{
    9663    fieldLabel unitPrice :: (Save r, Num t) => r -> t
    97 
    98 (Again the r{ ... } gets added as a further constraint.)
     64}}}
     65(Again the `r{ ... }` gets added as a further constraint.)
    9966
    10067Now we can use the field in a record, and that in effect declares an instance for the field/record. All these definitions are in the same module:
    101 
     68{{{
    10269    data Customer_NameAddress = ... (as above)
    10370    data Customer_Price a = Num a => Cust_Price {
     
    10774                                       ... }
    10875    data Customer_Order = Cust_Order { customer_id :: Int, ... }
    109 
     76}}}
    11077Then a field selection expression like:
    111     ... (customer_id r) ...          -- H98 style field application
    112 uses familiar type instance resolution to figure out from record type `r' how to extract the customer_id.
     78    `... (customer_id r) ...`          -- H98 style field application
     79uses familiar type instance resolution to figure out from record type `r` how to extract the `customer_id`.
    11380
    11481[Possibly that expression could be:
    115      ... r.customer_id ...
     82     `... r.customer_id ...`
    11683See <Dot as Postfix Func Apply> for that dot notation, but note that nothing in this proposal assumes dot notation will be needed.]
    11784
    118 From here upwards, the r{ ... } constraint is just a constraint, and gets merged with other constraints. For example, you could define a function:
    119 
     85From here upwards, the `r{ ... }` constraint is just a constraint, and gets merged with other constraints. For example, you could define a function:
     86{{{
    12087    fullName r = (firstName r) ++ " " ++ (lastName r)  -- per SPJ
    121 
     88}}}
    12289The type inferred would be:
    123 
     90{{{
    12491    fullName :: r{ firstName, lastName :: String} => r -> String
    125 
     92}}}
    12693which is eliding:
    127 
     94{{{
    12895    fullName :: (r{ firstName :: String}, r{ lastName :: String })
    12996                 => r -> String
    130 
     97}}}
    13198And if you think that's very close to the type of a field selector function, you'd be right. Here's some more examples of pseudo- or 'virtual' fields, using dot notation:
    132 
     99{{{
    133100    customer.fullName
    134101    shape.area
     
    138105    list.head
    139106    list.length
    140 
     107}}}
    141108[Since they're just functions, they can use dot notation -- or not: personal preference.]
    142109
    143110
    144 Modules and qualified names for records
     111=== Modules and qualified names for records ===
    145112
    146113Do these field selector functions have a special scope in some way? No! They're just functions. They can be exported/imported.
    147114
    148 We can't stop some other developer creating an application/package with a field customer_id which is incompatible with ours. (Say a Sales Order entry application where customer_id is a String, to merge with our Accounts Receivable.) So do we have a problem if someone wants to import both?
     115We can't stop some other developer creating an application/package with a field `customer_id` which is incompatible with ours. (Say a Sales Order entry application where `customer_id` is a `String`, to merge with our Accounts Receivable.) So do we have a problem if someone wants to import both?
    149116
    150117No! This is regular business-as-usual familiar name clash, and it's what the module system is designed to handle. The field selectors are just functions, we can use them qualified:
    151 
     118{{{
    152119    (My.customer_id myCust)        <===> myCust.My.customer_id
    153120    (Their.customer_id theirCust)  <===> theirCust.Their.customer_id
    154121    (My.customer_id r)       -- fails if r is from the 'other' module
    155 
    156 Import/Export and Representation hiding
     122}}}
     123
     124=== Import/Export and Representation hiding ===
    157125
    158126[See <No Mono Record Fields>, which is implied by DORF.]
     
    161129
    162130The field selector function is separately declared vs. the records and their fields, so must be exported separately. For example:
    163 
     131{{{
    164132{-# OPTIONS_GHC -XDeclaredOverloadedRecordFields             #-}
    165133module M( x )       where
    166134    fieldLabel x,y :: r -> Int
    167135    data T = MkT { x, y :: Int }
    168 
    169 Here only the field selector function `x' is exported. The representation is abstract, the client can't construct or dismantle a record type `T'; field `y' is hidden altogether.
     136}}}
     137Here only the field selector function `x` is exported. The representation is abstract, the client can't construct or dismantle a record type `T`;
     138
     139 field `y` is hidden altogether.
    170140
    171141If you say:
    172 
     142{{{
    173143{-# OPTIONS_GHC -XDeclaredOverloadedRecordFields
    174144                -XNoMonoRecordFields                   #-}
     
    176146    fieldLabel x,y :: r -> Int
    177147    data T = MkT { x, y :: Int }
    178 
    179 then you are exporting the x field within record type T, but _not_ the field selector x (nor the generated type 'peg' Proxy_x).
    180 
    181 Type T and field label `x' are exported, but not data constructor MkT, so `x' is unusable.
    182 
    183 The existence of field `y' is hidden altogether.
    184 
    185 
    186 
    187 Field Update for Overloadable Record Fields
     148}}}
     149then you are exporting the `x` field within record type `T`, but _not_ the field selector `x` (nor the generated type 'peg' `Proxy_x`).
     150
     151Type `T` and field label `x` are exported, but not data constructor `MkT`, so `x` is unusable.
     152
     153The existence of field `y` is hidden altogether.
     154
     155
     156
     157=== Field Update for Overloadable Record Fields ===
    188158
    189159You can (continue to) use pattern matching and data constructor tagging for record update:
    190 
     160{{{
    191161    case r of {
    192162     Cust_Price {unit_Price, ..}
    193163          -> Cust_Price {unit_Price = unit_Price * 1.05, .. }
    194164    }         -- increases Price by 5%
    195 
     165}}}
    196166(This uses ‑XDisambiguateRecordFields, -XRecordWildCards and ‑XNamedFieldPuns -- all mature GHC extensions.)
    197167
    198168The new part is polymorphic record update:
    199 
     169{{{
    200170    myPrice{ unit_Price = 72 :: Int }
    201 
    202 Returns a record with same fields as myPrice, except a different unit_Price. Note that the update can change the type of a field (if the record declaration is polymorphic).
    203 
    204 Note that upon first encountering that expression, we don't know the record types (because unit_Price is overloaded). So the types initially inferred are:
    205 
     171}}}
     172Returns a record with same fields as `myPrice`, except a different `unit_Price`. Note that the update can change the type of a field (if the record declaration is polymorphic).
     173
     174Note that upon first encountering that expression, we don't know the record types (because `unit_Price` is overloaded). So the types initially inferred are:
     175{{{
    206176    <expr>  :: r { unit_Price :: Int } => r
    207177    myPrice :: _r{ unit_Price :: t }   => _r
    208 
     178}}}
    209179That is, the update might be changing the record type as well as the field type -- in case that the record type is parametric over the field type.
    210180
    211 Behind the scenes, the update syntax with an expression prefix to the { ... } is syntactic sugar for a call to the polymorphic record update method `set':
    212 
     181Behind the scenes, the update syntax with an expression prefix to the `{ ... }` is syntactic sugar for a call to the polymorphic record update method `set`:
     182{{{
    213183    set (undefined :: Proxy_unit_Price) (72 :: Int) myPrice
    214 
     184}}}
    215185[See <DORF -- Implementor's view> for what the Proxy is doing.]
    216186
    217 Normal type inference/instance resolution will find the record type for myPrice, and therefore the correct instance to apply the update.
     187Normal type inference/instance resolution will find the record type for `myPrice`, and therefore the correct instance to apply the update.
    218188
    219189You can update multiple fields at the same time:
    220 
     190{{{
    221191    myCustNA { firstName = "Fred", lastName = "Dagg" }
    222 
    223 [There's a poor story to tell here in implementation terms: we split into two calls to `set', one nested inside the other. It's wasteful to build the intermediate record. Worse, the two fields' types might be parametric in the record type or polymorphically related (perhaps one is a method to apply to the other), then we get a type failure on the intermediate record.]
    224 
    225 Some 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'.
    226 
    227 
    228 
    229 ------------------- DORF -- Implementor's view -------------------
     192}}}
     193[There's a poor story to tell here in implementation terms: we split into two calls to `set`, one nested inside the other. It's wasteful to build the intermediate record. Worse, the two fields' types might be parametric in the record type or polymorphically related (perhaps one is a method to apply to the other), then we get a type failure on the intermediate record.]
     194
     195Some 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`.
     196
     197
     198
     199= DORF -- Implementor's view =
    230200
    231201The implementation has been prototyped in GHC 7.2.1, see
     
    247217
    248218There is to be a new declaration type, examples:
    249 
     219{{{
    250220    fieldLabel customer_id :: r -> Int
    251221    fieldLabel unitPrice :: (Save r, Num t) => r -> t
    252222    fieldLabel monoField :: Rec -> String   -- equiv to H98
    253 
    254 [`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!]
     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!]
    255225
    256226The fieldLabel declaration desugars to:
    257 
     227{{{
    258228    data Proxy_customer_id                  -- phantom, a type 'peg'
    259229    customer_id :: r{ customer_id :: Int } => r -> Int
     
    261231
    262232    unit_Price :: (r{ unit_Price :: t}, Save r, Num t) => r -> t
    263 
    264 That is: the r{ ... } constraint is added by the desugarer (and will be further desugarred to a `Has' constraint):
    265 
    266 Syntactic sugar for `Has'
     233}}}
     234That is: the `r{ ... }` constraint is added by the desugarer (and will be further desugarred to a `Has` constraint):
     235
     236Syntactic sugar for `Has`
    267237
    268238DORF steals from SORF:
    269 
     239{{{
    270240    r{ fld :: t }  <===> Has r Proxy_fld t
    271 
     241}}}
    272242Using the sugar in the surface syntax (representation) allows for some freedom in the design space 'behind the scenes'.
    273243
    274 Should `get' have a Proxy argument?
    275 I've used a phantom/proxy type (in GHC v 7.2.1) to drive type instancing for `Has'.
    276 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).
     244Should `get` have a Proxy argument?
     245I've used a phantom/proxy type (in GHC v 7.2.1) to drive type instancing for `Has`.
     246SORF 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).
    277247I'll leave it to the implementors to determine which works best.
    278248
    279 `get' is a method of the `Has' class:
     249`get` is a method of the `Has' class:
     250{{{
    280251    get :: (Has r fld t) => r -> fld -> t
    281 
     252}}}
    282253Record declaration
     254{{{
    283255    data Customer_NameAddress = Cust_NA { customer_id :: Int, ... }
    284 
    285 Does _not_ create a field selector function customer_id. Instead it creates a `Has' instance:
    286 
     256}}}
     257Does _not_ create a field selector function `customer_id`. Instead it creates a `Has` instance:
     258{{{
    287259    instance (t ~ Int)
    288260       => Has Customer_NameAddress Proxy_customer_id t where
    289261               get Cust_NA{customer_id} = customer_id
    290 
    291 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.
    292 The definition of `get' uses ‑XDisambiguateRecordFields style (with ‑XNamedFieldPuns).
    293 [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:
    294 data ... = Cust_NA { customer_id, custName :: String, ... }
    295 currently means customer_id is to be same type as custName.
     262}}}
     263Note 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.
     264The definition of `get` uses ‑XDisambiguateRecordFields style (with ‑XNamedFieldPuns).
     265
     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}}}
     270currently means `customer_id` is to be same type as `custName`.
     271
    296272Opportunity for improvement! ]
    297273
    298 Record/field update
    299 Update uses method `set' from the `Has' class:
     274=== Record/field update ===
     275Update uses method `set` from the `Has` class:
     276{{{
    300277        set :: (Has r fld t) => fld -> t -> r -> r
    301 `set's instances are defined using explicit data constructor:
     278}}}
     279`set`s instances are defined using explicit data constructor:
    302280    instance (t ~ String) =>
    303281        Has Customer_NameAddress Proxy_firstName t where