
Hi, Einar Karttunen wrote:
Here is a proposal to allow polymorphic record update for the existing record system. I think it makes sense if the current record system is kept.
Given a record like:
data Foo a = Foo { bar :: a }
it would be nice to be able to update it like:
f = Foo { bar = 'a' } g = f { bar = False }
constructing the new record by hand is quite tedious for records with many fields and one ends up writing helper functions to do this.
The above actually does work as far as I can tell (and GHCi agrees)? However, it is realated to a problem with the current record system that has annoyed me for years, and I suspect that is what Einar is referring to. If the current record system essentially is kept for Haskell', it would definitely be worth considering fixing that problem. I started writing up a proposal a while ago, but never quite got around to finalizing it. It's enclosed, but thus does have some rough edges and is lacking some detail. But I hope it explains the problem and outlines a reasonable solution. If it does not get shot down immediately, I'll put it on the Wiki. I think there is a page for tweaks to the record system there. Best, /Henrik -- Henrik Nilsson School of Computer Science and Information Technology The University of Nottingham nhn@cs.nott.ac.uk This message has been checked for viruses but the contents of an attachment may still contain software viruses, which could damage your computer system: you are advised to perform your own checks. Email communications with the University of Nottingham may be monitored as permitted by UK legislation. Polymorphic Record Update ------------------------- Consider the following data type: data T a = C1 { f1 :: a } | C2 { f1 :: a, f2 :: Int } | C3 { f2 :: Int } deriving Show Suppose we want to update the field "f1" only in such a way that its type changes. We cannot use the record update syntax, as not all constructors have a field "f1". So we write a utility function. However, we would prefer to do as little as possible when it comes to values constructed by constructors NOT having a field "f2". One might naively try this: foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x But of course, this does not type check as the type of "x" is different on the LHS and RHS. We can get around that by reconstructing the value on the RHS: foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x@(C3 {f2 = n}) = C3 {f2 = n} However, this is bad, because we have to change the code if further constructors are added, even when they do not have a field "f1", and we also have to change the code if further fields are added to constructors not having the field "f1". This is tedious, error prone, and really defeats one of the main reasons for using records in the first place. For example: data T a = C1 { f1 :: a } | C2 { f1 :: a, f2 :: Int } | C3 { f2 :: Int, f3 :: Char } | C4 { f2 :: Int } deriving Show foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x@(C3 {f2 = n, f3 = c}) = C3 {f2 = n, f3 = c} foo x@(C4 {f2 = n}) = C4 {f2 = n} One might think it would be possible to do better if we're furtunate enough to have a field that is common to *all* constructors not having a field "f1", as is the case for "f2" in this case: foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x {f2 = f2 x} But this does not type check, and it would not apply anyway if there is no such common field. What we really need is a function that reconstructs a value of type "T a" at type "T b" for all values constructed by a constructor that does not have a field "f1": coerce_no_f1 :: T a -> T b coerce_no_f1 x@(C3 {f2 = n, f3 = c}) = C3 {f2 = n, f3 = c} coerce_no_f1 x@(C4 {f2 = n}) = C4 {f2 = n} foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = coerce_no_f1 x But we'd rather not have to write such functions by hand, just as we'd rather not write update functions by hand. Maybe the record update syntax could be extended so that the function that gets generated behind the scenes only includes constructors that does NOT mention a particular field. For example, the field name(s) that must not occur could be prefixed by "~" which suggests negation in some settings. It does not have this connotation in Haskell, but at least "~" is already a special symbol. We could then write: foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x {~f1} Now the code for "foo" only has to be changed if new constructors having a field "f1" are added. Of course, it should be possible to combine this with the normal record update syntax. E.g. foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x {~f1, f2 = f2 x + 1}