Changes between Version 10 and Version 11 of ExistingRecords


Ignore:
Timestamp:
Mar 29, 2006 2:53:36 PM (9 years ago)
Author:
nhn@…
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ExistingRecords

    v10 v11  
    6969Note the change in the type of the stored field.
    7070At the moment, such a record update must be written using the data constructor, not the update syntax.
    71 
    7271== 'Open' statement ==
    7372
     
    9897This would insure that by not exporting a field label, it cannot be gotten around by using positional notation.
    9998This fix would also require the polymorphic setting ability mentioned above and would partially mitigate the need for ReadonlyConstructors
     99
     100== Polymorphic Record Update take II ==
     101
     102(The following was discussed briefly on the Haskell' list.)
     103Consider the following data type:
     104{{{
     105data T a
     106  = C1 { f1 :: a }
     107  | C2 { f1 :: a, f2 :: Int }
     108  | C3 { f2 :: Int }
     109deriving Show
     110}}}
     111Suppose we want to update the field `f1` only in such a way that
     112its type changes. We cannot use the record update syntax, as not
     113all constructors have a field `f1`. So we write a utility function.
     114However, we would prefer to do as little as possible when it
     115comes to values constructed by constructors NOT having a field
     116`f2`. One might naively try this:
     117{{{
     118foo :: T a -> T Int
     119foo x@(C1 {}) = x {f1 = 1}
     120foo x@(C2 {}) = x {f1 = 2}
     121foo x         = x
     122}}}
     123But of course, this does not type check as the type of `x` is
     124different on the LHS and RHS. We can get around that by reconstructing
     125the value on the RHS:
     126{{{
     127foo :: T a -> T Int
     128foo x@(C1 {})       = x {f1 = 1}
     129foo x@(C2 {})       = x {f1 = 2}
     130foo x@(C3 {f2 = n}) = C3 {f2 = n}
     131}}}
     132However, this is bad, because we have to change the code if further
     133constructors are added, even when they do not have a field `f1`,
     134and we also have to change the code if further fields are added
     135to constructors not having the field `f1`. This is tedious,
     136error prone, and really defeats one of the main reasons for using
     137records in the first place. For example:
     138{{{
     139data T a
     140  = C1 { f1 :: a }
     141  | C2 { f1 :: a, f2 :: Int }
     142  | C3 { f2 :: Int, f3 :: Char }
     143  | C4 { f2 :: Int }
     144  deriving Show
     145
     146foo :: T a -> T Int
     147foo x@(C1 {})               = x {f1 = 1}
     148foo x@(C2 {})               = x {f1 = 2}
     149foo x@(C3 {f2 = n, f3 = c}) = C3 {f2 = n, f3 = c}
     150foo x@(C4 {f2 = n})         = C4 {f2 = n}
     151}}}
     152One might think it would be possible to do better if we're furtunate
     153enough to have a field that is common to *all* constructors not having
     154a field `f1`, as is the case for `f2` in this case:
     155{{{
     156foo :: T a -> T Int
     157foo x@(C1 {}) = x {f1 = 1}
     158foo x@(C2 {}) = x {f1 = 2}
     159foo x         = x {f2 = f2 x}
     160}}}
     161But this does not type check, and it would not apply anyway if
     162there is no such common field.
     163
     164What we really need is a function that reconstructs a value of type `T a`
     165at type `T b` for all values constructed by a constructor that does not have
     166a field `f1`:
     167{{{
     168coerce_no_f1 :: T a -> T b
     169coerce_no_f1 x@(C3 {f2 = n, f3 = c}) = C3 {f2 = n, f3 = c}
     170coerce_no_f1 x@(C4 {f2 = n})         = C4 {f2 = n}
     171
     172foo :: T a -> T Int
     173foo x@(C1 {}) = x {f1 = 1}
     174foo x@(C2 {}) = x {f1 = 2}
     175foo x         = coerce_no_f1 x
     176}}}
     177But we'd rather not have to write such functions by hand, just as
     178we'd rather not write update functions by hand. Maybe the record
     179update syntax could be extended so that the function that gets
     180generated behind the scenes only includes constructors that
     181does NOT mention a particular field. For example, the field
     182name(s) that must not occur could be prefixed by `~` which suggests
     183negation in some settings. It does not have this connotation in Haskell,
     184but at least `~` is already a special symbol. We could then write:
     185{{{
     186foo :: T a -> T Int
     187foo x@(C1 {}) = x {f1 = 1}
     188foo x@(C2 {}) = x {f1 = 2}
     189foo x         = x {~f1}
     190}}}
     191Now the code for `foo` only has to be changed if new constructors
     192having a field `f1` are added.
     193
     194Of course, it should be possible to combine this with the normal
     195record update syntax. E.g.
     196{{{
     197foo :: T a -> T Int
     198foo x@(C1 {}) = x {f1 = 1}
     199foo x@(C2 {}) = x {f1 = 2}
     200foo x         = x {~f1, f2 = f2 x + 1}
     201}}}
    100202
    101203= Meta-Proposal =