(small) records proposal for Haskell '06
Hi all, I'd like to humbly submit a conservative proposal for changes to the records mechanism for Haskell '06. The plan for Haskell '06 (to the extent that there is one) is to make it a conservative modification of Haskell 98, which is a good idea. As such, I don't think features should be added to the language that haven't been both implemented and tested, and the problem with the whole records problem is that pretty much every proposed improvement is *not* backwards-compatible with Haskell 98, and therefore hasn't been either implemented or tried out. My idea is to make a small change, which will also not be backwards-compatible with Haskell 98, but will *almost* be, and will be hopefully forward-compatible with most complete solutions. At a minimum, the proposed change will allow the prototyping of real solutions as a non-invasive preprocessor a la DrIFT. My proposal is simply to remove the automatic declaration of accessor functions. In Haskell 98, data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int } desugars to something like data FooBar = Foo Int | FooBar Int Int foo :: FooBar -> Int foo (Foo f) = f foo (FooBar f _) = f bar :: FooBar -> Int bar (Foo _) = error "bad Foo" bar (FooBar _ b) = b plus additional sugar for constructors and pattern matching. I would leave the sugar for constructors and pattern matching in place, but remove the automatic declaration of functions "foo" and "bar". This change "solves" the problem that different records in a single namespace cannot share field names in a simple manner. In order to allow the writing of records code with is both valid Haskell 98 and valid Haskell '06, I'd suggest a pragma that causes the Haskell '06 compiler to generate the accessor functions. It's a bit ugly, but seems best to me. An alternative would be a standard TH function to do the same, but I don't know if TH is going to be included in Haskell '06. This change may look like a step backwards (which it is), but I think that it would allow significant steps forward. One open question (in my mind) would be whether we'd allow data Foo = FooInt { foo :: Int } | FooChar { foo :: Char } In the "new" system, there's no reason this need be illegal. -- David Roundy http://www.darcs.net
David, Say you turned automatic generation of accessor functions off. You would still need accessor functions, right? What would they look like if you were to write them by hand? Thanks, Joel On Jan 2, 2006, at 1:43 PM, David Roundy wrote:
This change "solves" the problem that different records in a single namespace cannot share field names in a simple manner. In order to allow the writing of records code with is both valid Haskell 98 and valid Haskell '06, I'd suggest a pragma that causes the Haskell '06 compiler to generate the accessor functions.
On Mon, Jan 02, 2006 at 01:53:51PM +0000, Joel Reymont wrote:
Say you turned automatic generation of accessor functions off. You would still need accessor functions, right? What would they look like if you were to write them by hand?
They could look identical if you wrote them by hand, but you could also choose to put them in a type class. So you might write data Foo = Foo { foo :: Int } data FooBar = FooBar { foo :: Int, bar :: String } class Foo f where foo :: f -> Int instance Foo Foo where foo (Foo { foo=f }) = f instance Foo FooBar where foo (FooBar { foo=f }) = f bar :: FooBar -> String bar (FooBar { bar=b }) = b I believe it wouldn't be hard to write TH (or SYB, or DrIFT or something else?) code to do this sort of thing automatically. Technically, you don't need accessor functions, as you can get by with pattern matching, which is what this proposal is about. If we remove the accessor functions, they can always be written by hand, and we separate the record-field namespace from the function namespace. One might argue that updater functions are as important as accessor functions, but they aren't defined automatically in Haskell 98. One might prefer to define data Foo = Foo { foo :: Int } set_foo :: Foo -> Int -> Foo get_foo :: Foo -> Int and perhaps even modify_foo :: Foo -> (Int -> Int) -> Foo which would eliminate the need for the (clumsy, in my opinion) { foo=f } syntax. -- David Roundy http://www.darcs.net
On 1/2/06, David Roundy <droundy@abridgegame.org> wrote:
Hi all,
I'd like to humbly submit a conservative proposal for changes to the records mechanism for Haskell '06. The plan for Haskell '06 (to the extent that there is one) is to make it a conservative modification of Haskell 98, which is a good idea. As such, I don't think features should be added to the language that haven't been both implemented and tested, and the problem with the whole records problem is that pretty much every proposed improvement is *not* backwards-compatible with Haskell 98, and therefore hasn't been either implemented or tried out.
First of all, I do think your proposal is better than the current situation, but only marginally so. I know I'm probably in the minority here, but I think the current Haskell records system is so horribly broken that it's basically impossible to end up with something worse, regardless of which of the existing records proposals you go with. And I agree strongly with Joel Reymont and others who basically say that one of the largest obstacles for Haskell being used in the "real world" is its records system. So personally I think that there should be some careful consideration (and implementation, of course) of the records proposals and then just make an educated "guess" as to which would work best in practice and go with it. All of the existing proposals are a lot better than the existing mechanism, even if they're not "optimal". Yes I know H'06 is supposed to be mainly a standardization of existing extensions, but as I said, the records system is so broken that I for one think its "fix" should be accelerated even to the point of including a brand new feature into an otherwise conservative standardisation process. You could view H'06 as the "practice run" for a next-gen records mechanism to be considered for Haskell 2.0. /S -- Sebastian Sylvan +46(0)736-818655 UIN: 44640862
In article <20060102134354.GA15796@abridgegame.org>, David Roundy <droundy@abridgegame.org> wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal.
How would this behave? data Foo a b = FooA {foo :: a} | FooB {foo :: b} -- Ashley Yakeley, Seattle WA
On Mon, Jan 02, 2006 at 04:23:32PM -0800, Ashley Yakeley wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal.
How would this behave?
data Foo a b = FooA {foo :: a} | FooB {foo :: b}
I'm not sure I understand the problem. Why would there be any difficulty with this? -- David Roundy http://www.darcs.net
David Roundy wrote:
On Mon, Jan 02, 2006 at 04:23:32PM -0800, Ashley Yakeley wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal.
How would this behave?
data Foo a b = FooA {foo :: a} | FooB {foo :: b}
I'm not sure I understand the problem. Why would there be any difficulty with this?
What type would "foo" have? And what would that imply for the type of "foo" in your Int/Char example? -- Ashley Yakeley
On Tue, Jan 03, 2006 at 02:41:40PM -0800, Ashley Yakeley wrote:
David Roundy wrote:
On Mon, Jan 02, 2006 at 04:23:32PM -0800, Ashley Yakeley wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal. How would this behave?
data Foo a b = FooA {foo :: a} | FooB {foo :: b} I'm not sure I understand the problem. Why would there be any difficulty with this? What type would "foo" have? ...
Nothing, since in David's proposal there would be no 'foo' defined on the top level at all. Peace, Dylan
On Jan 4, 2006, at 2:25 AM, Dylan Thurston wrote:
On Tue, Jan 03, 2006 at 02:41:40PM -0800, Ashley Yakeley wrote:
David Roundy wrote:
On Mon, Jan 02, 2006 at 04:23:32PM -0800, Ashley Yakeley wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal. How would this behave?
data Foo a b = FooA {foo :: a} | FooB {foo :: b} I'm not sure I understand the problem. Why would there be any difficulty with this? What type would "foo" have? ...
Nothing, since in David's proposal there would be no 'foo' defined on the top level at all.
What happens to record updates? setFoo x r = r { foo = x } Or is the proposal to remove updates as well? / Ulf
On Wed, Jan 04, 2006 at 11:53:59AM +0100, Ulf Norell wrote:
On Jan 4, 2006, at 2:25 AM, Dylan Thurston wrote:
On Tue, Jan 03, 2006 at 02:41:40PM -0800, Ashley Yakeley wrote:
David Roundy wrote:
On Mon, Jan 02, 2006 at 04:23:32PM -0800, Ashley Yakeley wrote:
One open question (in my mind) would be whether we'd allow
data Foo = FooInt { foo :: Int } | FooChar { foo :: Char }
In the "new" system, there's no reason this need be illegal. How would this behave?
data Foo a b = FooA {foo :: a} | FooB {foo :: b} I'm not sure I understand the problem. Why would there be any difficulty with this? What type would "foo" have? ...
Nothing, since in David's proposal there would be no 'foo' defined on the top level at all.
What happens to record updates?
setFoo x r = r { foo = x }
Or is the proposal to remove updates as well?
Ah, good point, I hadn't thought about that. My proposal was to keep record updates which would indeed mean that we'd need to require that all "foo"s in a given data type have the same type. Which I don't think is a bad thing either... it just hadn't occurred to me. -- David Roundy http://www.darcs.net
Hello, On 1/4/06, David Roundy <droundy@abridgegame.org> wrote:
What happens to record updates?
setFoo x r = r { foo = x }
Or is the proposal to remove updates as well?
Ah, good point, I hadn't thought about that. My proposal was to keep record updates which would indeed mean that we'd need to require that all "foo"s in a given data type have the same type. Which I don't think is a bad thing either... it just hadn't occurred to me.
This is not the problem... What is the type of the 'setFoo' function? When you think about that, you are likely to run into the usual 'Has' or 'Lacks' predicates in the types. I think the same pattern pops up whenever you want to have labels access fields (for projection or update) in records of different types, because the evidence for the predicates tells you where in this particular type to find the field. Another option is of course to somehow modify the syntax so that record update specifies at what type we want to perform the update. In any case these seem like rather substantial changes that have not been tested nearly as well as say the Trex or other record systems out there. -Iavor
On Wed, Jan 04, 2006 at 08:55:39AM -0500, David Roundy wrote:
Ah, good point, I hadn't thought about that. My proposal was to keep record updates which would indeed mean that we'd need to require that all "foo"s in a given data type have the same type. Which I don't think is a bad thing either... it just hadn't occurred to me.
The issue is with multiple data types that have the same field name, not the same field reused within a data type. for instance data Foo = Foo { foo :: Int } data Bar = Bar { foo :: Int } f x = x { foo = 4 } what type does f have? f :: Foo -> Foo or f :: Bar -> Bar ? John -- John Meacham - ⑆repetae.net⑆john⑈
On Wed, Jan 04, 2006 at 03:03:40PM -0800, John Meacham wrote:
On Wed, Jan 04, 2006 at 08:55:39AM -0500, David Roundy wrote:
Ah, good point, I hadn't thought about that. My proposal was to keep record updates which would indeed mean that we'd need to require that all "foo"s in a given data type have the same type. Which I don't think is a bad thing either... it just hadn't occurred to me.
The issue is with multiple data types that have the same field name, not the same field reused within a data type. for instance
data Foo = Foo { foo :: Int } data Bar = Bar { foo :: Int }
f x = x { foo = 4 }
what type does f have?
f :: Foo -> Foo or f :: Bar -> Bar ?
Ah yes, that would seem to be a problem. The only solution I can imagine would be to implement a class for each field name. i.e. the only reasonble type of f I can imagine is something like f :: Integral i, RecordHasField_foo i r => r -> r But that's a very complicated solution, and once one implemented that solution, one would no longer need to remove the accessor functions (since they could be stuck in the class), which would obsolete this whole idea... :( -- David Roundy http://www.darcs.net
David Roundy wrote:
The only solution I can imagine would be to implement a class for each field name. i.e. the only reasonble type of f I can imagine is something like
f :: Integral i, RecordHasField_foo i r => r -> r
But that's a very complicated solution, and once one implemented that solution, one would no longer need to remove the accessor functions (since they could be stuck in the class), which would obsolete this whole idea...
There is no need to speak of this solution in subjunctive mood. It *has* been implemented. If you wish, you can try at right now, with the existing GHC (e.g., 6.2, 6.4 or 6.4.1), using the Cabal distribution of HList/OOHaskell very kindly made by Einar Karttunen. Here's an example:
{-# OPTIONS -fglasgow-exts #-} {-# OPTIONS -fallow-undecidable-instances #-}
module Test where
import OOHaskell -- big overkill but convenient: it's only one line...
accessor r f = r # f
setter r f v = r .@. (f,v)
*Test> :t accessor accessor :: (HasField l r v) => r -> l -> v Couldn't be any simpler... *Test> :t setter setter :: (HZip x l' l, HUpdateAtHNat n b y l', HFind a x n, HZip x y r) => Record r -> a -> b -> Record l Admittedly, that type can be simpler (just as the type of the accessor). But the getter is optimized (including its type) because we use it all the time. The setter is based on a generic (and hence less optimal) code. I guess we don't use setters often, so we never got around to making even trivial optimizations... Something to do for the next Haskell Community Report.
How does pattern matching work with HList? I would like to pass a HList to a function and only match if a certain field had a certain value. On Jan 6, 2006, at 7:14 AM, oleg@pobox.com wrote:
*Test> :t accessor accessor :: (HasField l r v) => r -> l -> v
Joel Reymont wrote:
How does pattern matching work with HList? I would like to pass a HList to a function and only match if a certain field had a certain value.
The code below defines the function foo that accepts a record and yields one value if the field PtX of the record has the value 0. If the field has any other value, a different result is returned. The function is written in a pattern-matching style. Also, the function is record-polymorphic: it takes _any_ record (of any `record type') that happens to have the field names PtX. *Test> :t foo foo :: (Num v, HasField (Proxy PtX) r v) => r -> [Char] David Roundy wrote:
I guess I meant to say that it hadn't been implemented for "real" records, and there doesn't seem to be a consensus that it's the best approach.
There had been no argument about what is best. As I understood it, the question was about a specific proposed behavior in a hypothetical record system. I merely wanted to point out that one does not need to _guess_ how that feature might work in practice. One can try it right now, and see for oneself if it works for the problem at hand or not. {-# OPTIONS -fglasgow-exts #-} {-# OPTIONS -fallow-undecidable-instances #-} {-# OPTIONS -fallow-overlapping-instances #-} module Test where import OOHaskell -- big overkill but convenient: it's only one line... -- Labels -- The more convenient labels need -fallow-overlapping-instances -- The less convenient label representation needs fewer extensions. -- We go for more convenient... data PtX; px = proxy::Proxy PtX data PtY; py = proxy::Proxy PtY accessor r f = r # f setter r f v = (f,v) .<. r point1 x = px .=. x .*. emptyRecord point2 x y = px .=. x .*. py .=. (y + 10) .*. emptyRecord -- Record-polymorphic function foo p | 0 <- p # px = "X is zero" foo _ = "something else" test1 = foo (point1 0) test1' = foo (point1 42) test2 = foo (point2 10 20) -- inline construction of the record test3 = foo (py .=. False .*. px .=. 0 .*. emptyRecord)
On Thu, Jan 05, 2006 at 11:14:18PM -0800, oleg@pobox.com wrote:
David Roundy wrote:
The only solution I can imagine would be to implement a class for each field name. i.e. the only reasonble type of f I can imagine is something like
f :: Integral i, RecordHasField_foo i r => r -> r
But that's a very complicated solution, and once one implemented that solution, one would no longer need to remove the accessor functions (since they could be stuck in the class), which would obsolete this whole idea...
There is no need to speak of this solution in subjunctive mood. It *has* been implemented. If you wish, you can try at right now, with the existing GHC (e.g., 6.2, 6.4 or 6.4.1), using the Cabal distribution of HList/OOHaskell very kindly made by Einar Karttunen. Here's an example:
I guess I meant to say that it hadn't been implemented for "real" records, and there doesn't seem to be a consensus that it's the best approach.
accessor r f = r # f
*Test> :t accessor accessor :: (HasField l r v) => r -> l -> v
Couldn't be any simpler...
Except that this type doesn't allow the accessor to have the same name as the field, since fields share the function namespace. Not that we have much choice here, but it means that this precise approach can't be made compatible with Haskell 98 as far as I can tell. -- David Roundy http://www.darcs.net
what type would f x = x { foo = "hello" } have if there were multiple types with 'foo' as a field name? John -- John Meacham - ⑆repetae.net⑆john⑈
On Mon, Jan 02, 2006 at 08:43:56AM -0500, David Roundy wrote:
data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int }
desugars to something like
data FooBar = Foo Int | FooBar Int Int
foo :: FooBar -> Int foo (Foo f) = f foo (FooBar f _) = f bar :: FooBar -> Int bar (Foo _) = error "bad Foo" bar (FooBar _ b) = b
I'm sure you know this, but I want to be explicit for everyone: If you keep the pattern-matching syntax, this could be coded more elegantly by hand, without counting positions in a record, by data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int } foo :: FooBar -> Int foo (Foo { foo = f }) = f foo (FooBar { foo = f}) = f bar :: FooBar -> Int bar (Foo _) = error "bad Foo" bar (FooBar { bar = f}) = f I think this proposal is great. Peace, Dylan
On Tue, Jan 03, 2006 at 08:25:01PM -0500, Dylan Thurston wrote:
On Mon, Jan 02, 2006 at 08:43:56AM -0500, David Roundy wrote:
data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int }
desugars to something like
data FooBar = Foo Int | FooBar Int Int
foo :: FooBar -> Int foo (Foo f) = f foo (FooBar f _) = f bar :: FooBar -> Int bar (Foo _) = error "bad Foo" bar (FooBar _ b) = b
I'm sure you know this, but I want to be explicit for everyone: If you keep the pattern-matching syntax, this could be coded more elegantly by hand, without counting positions in a record, by
Indeed...
data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int }
foo :: FooBar -> Int foo (Foo { foo = f }) = f foo (FooBar { foo = f}) = f
This makes me wonder (this being a separate idea) whether one could't allow pattern matching on the constructor foo (_ { foo = f }) = f In order for this to work, I think we'd have to declare a class for each field name, so that we'd have a class so that the type of foo would be something like foo :: FieldName_foo a r => r -> a ...but this is on the (controversial) subject of possible record replacements... which I am trying to avoid for the moment. :) And this sort of trick would involve type inference issues that I don't even want to get close to. -- David Roundy http://www.darcs.net
On 2006-01-02, David Roundy <droundy@abridgegame.org> wrote:
My proposal is simply to remove the automatic declaration of accessor functions. In Haskell 98,
data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int }
I would find this to be incredibly annoying. The fact that these accessor functions exist automatically is, IMHO, one of the very nice things about Haskell records. I have nightmares of manually coding up hundreds of get* and set* functions in Java from your proposal ;-) I also would consider it a bug to have something so annoying that a code-generation tool is required to work with it sanely. Perhaps there is another approach. In your example, perhaps we could have: class FooClass a where foo :: a -> Int instance FooClass Foo where foo (Foo x) = x instance FooClas FooBar where foo (FooBar x) = x I don't know if compilers can do this automatically, but by importing modules qualified, you can do this in Haskell today. I believe this approximates the OCaml extension to solve the same problem. -- John
On Wed, Jan 04, 2006 at 02:17:27PM +0000, John Goerzen wrote:
On 2006-01-02, David Roundy <droundy@abridgegame.org> wrote:
My proposal is simply to remove the automatic declaration of accessor functions. In Haskell 98,
data FooBar = Foo { foo :: Int } | FooBar = { foo :: Int, bar :: Int }
I would find this to be incredibly annoying. The fact that these accessor functions exist automatically is, IMHO, one of the very nice things about Haskell records. I have nightmares of manually coding up hundreds of get* and set* functions in Java from your proposal ;-)
Haskell 98 already requires you to code up set* functions (provided you want them), so I'd only be doubling the amount of work you need to do. If you can get by with the existing non-function update syntax, then you can also get by with pattern matching to access record contents.
I also would consider it a bug to have something so annoying that a code-generation tool is required to work with it sanely.
That's why it would only be good as an interim measure, and why you'd need the pragma to revert to Haskell 98 behavior for records defined in a given module.
Perhaps there is another approach. In your example, perhaps we could have:
class FooClass a where foo :: a -> Int
instance FooClass Foo where foo (Foo x) = x
instance FooClas FooBar where foo (FooBar x) = x
I don't know if compilers can do this automatically, but by importing modules qualified, you can do this in Haskell today.
This is one of the proposed solutions that I'd like to make possible (although I've forgotten who proposed this particular one). The problem is that it's not compatible with Haskell 98, and noone seems interested in actually coding up an incompatible language dialect to experiment with improvements to the records system. -- David Roundy http://www.darcs.net
On 2006-01-04, David Roundy <droundy@abridgegame.org> wrote:
On Wed, Jan 04, 2006 at 02:17:27PM +0000, John Goerzen wrote: Haskell 98 already requires you to code up set* functions (provided you want them), so I'd only be doubling the amount of work you need to do. If you can get by with the existing non-function update syntax, then you can also get by with pattern matching to access record contents.
I have a lot of code where I rarely need to update records but frequently need to access them. For instance: http://darcs.complete.org/hdbc/doc/Database-HDBC.html Most of the functions here, such as fetchRow, are automatically-generated accessor functions. In the public API, record is represented by an abstract type. But in the API exposed to driver developers: http://darcs.complete.org/hdbc/doc/Database-HDBC-Types.html You can see it all at work. Having all these functions automatically-generated is very helpful here, and the fact that one can expose the function without the underlying datatype is also handy. That means, for instance, that end users need not know that fetchRow is provided by driver implementors while fetchAllRows is a generic function implemented in terms of lower-level ones. It all looks the same to them. It also means that this internal detail can be adjusted without breaking source compatibility with applications. And it also means that I don't have to waste time writing two dozen accessor functions, carefully counting the underscores each time.
That's why it would only be good as an interim measure, and why you'd need the pragma to revert to Haskell 98 behavior for records defined in a given module.
I think that the existing default is a good one, really. I could see myself turning on that pragma for everything I write, globally, if it came to that. -- John
This change "solves" the problem that different records in a single namespace cannot share field names in a simple manner.
As mentioned elsewhere, you'd also need to remove the functional update feature to fix this namespace problem.
In order to allow the writing of records code with is both valid Haskell 98 and valid Haskell '06, I'd suggest a pragma that causes the Haskell '06 compiler to generate the accessor functions.
I think it'd also be good to make it possible to "derive" the accessors, using the usual deriving(...) syntax. Which would be like turning on the pragma but at the granularity of a single datatype (or maybe even a single field). Stefan
participants (11)
-
Ashley Yakeley -
David Roundy -
Dylan Thurston -
Iavor Diatchki -
Joel Reymont -
John Goerzen -
John Meacham -
oleg@pobox.com -
Sebastian Sylvan -
Stefan Monnier -
Ulf Norell