Changes between Version 3 and Version 4 of Records/DeclaredOverloadedRecordFields


Ignore:
Timestamp:
Feb 17, 2012 10:55:49 PM (3 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