
Hello 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. - Einar Karttunen

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}

On 1/23/06, Henrik Nilsson
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}
Is this really necessary? Adding '~' seems less intuitive to me than just writing foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x or foo x = x {f2 = f2 x + 1} for the last example. From an implementor's point of view, if we expect the proper coercions to be inferred by the type checker it would still have to check that there are indeed no more fields than other than 'f1' that mention the parameter 'a', and also that there are no more constructors that mention 'f1'. Wouldn't it be just as simple to assert that for all the fields that mention 'a', none of these appear in any of the remaining constructors? On the other hand pattern matching would certainly be more expressive if '~' is added, so perhaps adding it has merit of its own. If we write foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x {~f1} there could still be more constructors in T a that do mention the 'f1' field, but there is no matching clause for them in the definition of 'foo'. But I would see that as a second separate proposal, e.g. a Proposal for Negation in Record Pattern Matching. Sure it would fit very well with the Polymorphic record update discussed here, but I would think they should be treated separately. /Niklas

Henrik, can you please be sure this is captured on the wiki somewhere?
(Sorry, I haven't checked to see if you already did that). Make
yourself the owner :)
peace,
isaac
Niklas Broberg
On 1/23/06, Henrik Nilsson
wrote: [snip lots of good stuff]
This suggestion would go a long way to alleviate the burden of boiler-plate coding. It is a conservative extension, and it is intuitive at that. Indeed I believe I have written code with the suggested update mechanism many times without thinking on the type mismatch (and been beaten on my fingers by the compiler of course). :-)
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}
Is this really necessary? Adding '~' seems less intuitive to me than just writing
foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x
or foo x = x {f2 = f2 x + 1}
for the last example. From an implementor's point of view, if we expect the proper coercions to be inferred by the type checker it would still have to check that there are indeed no more fields than other than 'f1' that mention the parameter 'a', and also that there are no more constructors that mention 'f1'. Wouldn't it be just as simple to assert that for all the fields that mention 'a', none of these appear in any of the remaining constructors?
On the other hand pattern matching would certainly be more expressive if '~' is added, so perhaps adding it has merit of its own. If we write
foo :: T a -> T Int foo x@(C1 {}) = x {f1 = 1} foo x@(C2 {}) = x {f1 = 2} foo x = x {~f1}
there could still be more constructors in T a that do mention the 'f1' field, but there is no matching clause for them in the definition of 'foo'. But I would see that as a second separate proposal, e.g. a Proposal for Negation in Record Pattern Matching. Sure it would fit very well with the Polymorphic record update discussed here, but I would think they should be treated separately.
/Niklas _______________________________________________ Haskell-prime mailing list Haskell-prime@haskell.org http://haskell.org/mailman/listinfo/haskell-prime
participants (4)
-
Einar Karttunen
-
Henrik Nilsson
-
Isaac Jones
-
Niklas Broberg