
Friends Provoked the (very constructive) Yesod blog post on "Limitations of Haskell", and the follow up discussion, I've started a wiki page to collect whatever ideas we have about the name spacing issue for record fields. http://hackage.haskell.org/trac/ghc/wiki/Records As Simon M said on Reddit, this is something we'd like to fix; but we need a consensus on how to fix it. Simon

I added my evaluation of the module-based approach to existing records, but on second thoughts it's maybe inappropriate, so I'll post it here. I saw that some people commented on the reddit discussion that the solution is to put your types in separate modules but it doesn't seem that anyone has tried to do this on a large scale. I tried it (and some other record paradigms including Has), but I don't think it scales. Here's why… Suppose I have 112 hand-crafted data types in my project (e.g. see attachment 51369.txt[1]), this creates a lot of conflicts in field names and constructor names. For example: {{{ data Comment = Comment { commentId :: CommentId , commentContent :: Content , commentReviewId :: ReviewId , commentSubmissionId :: SubmissionId , commentConferenceId :: ConferenceId , commentDate :: ISODate , commentReviewerNumber :: Int } deriving (Show) }}} This is a real type in my project. It has fields like “id”, “content”, “reviewId”, “submissionId”, “date”. There are seven other data types that have a field name “submissionId”. There are 15 with “conferenceId”. There are 7 with “content”. And so on. This is just to demonstrate that field clashes ''do'' occur ''a lot'' in a nontrivial project. It also demonstrates that if you propose to put each of these 112 types into a separate module, you are having a laugh. I tried this around the 20 type mark and it was, apart from being very slow at compiling, ''very'' tedious to work with. Creating and editing these modules was a distracting and pointless chore. It ''also'' demonstrated, to me, that qualified imports are horrible when used on a large scale. It happened all the time, that'd I'd import, say, 10 different data types all qualified. Typing map (Foo.id . BarMu.thisField) and foo Bar.Zot{x=1,y=2} becomes tedious and distracting, especially having to add every type module when I want to use a type. And when records use other types in other modules, you have ''a lot'' of redundancy. With the prefixing paradigm I'd write fooId and barMuThisField, which is about as tedious but there is at least less . confusion and no need to make a load of modules and import lines. Perhaps local modules would solve half of this problem. Still have to write “Bar.mu bar” rather than “mu bar”, but it'd be an improvement. I also have 21 Enum types which often conflict. I end up having to include the name of the type in the constructor, or rewording it awkwardly. I guess I should put these all in separate modules and import qualified, too. Tedious, though. At least in this case languages like C# and Java also require that you type EnumName.EnumValue, so c‘est la vie. [1]: http://hackage.haskell.org/trac/ghc/attachment/wiki/Records/51369.txt

Chris, Thank you for the real word experience report. I had assumed (because
everyone else told me) that importing qualified would be much, much better
than prefixing. I had thought that in your case since you are big on model
separation that you would have liked having a separate file for each model
to separate out all your model related code with. As a counter point, in all
of my (MVC) web application projects, we do have a separate file for each
model, and we like this approach. Each file usually contains a lot of
"business logic" related to the model- the only relatively empty model files
are ones that really represent embedded data. When I use MongoDB (which
actually supports embedded data instead of forcing you to create a separate
table), I will actually place the embedded models in the same file as the
model which includes them.
After my blog post complaining about records, I had a few people telling me
that I can just use existing polymorphism to avoid the name-spacing issue. I
collected the approaches here: http://www.yesodweb.com/wiki/record-hacks
I didn't think any of those telling me what i should do had actually tried
to do this themselves, particularly at any kind of larger scale. I am
interested to see if anyone has experience trying this approach, or if you
have considered it.
On Thu, Sep 15, 2011 at 3:00 AM, Christopher Done
I added my evaluation of the module-based approach to existing records, but on second thoughts it's maybe inappropriate, so I'll post it here. I saw that some people commented on the reddit discussion that the solution is to put your types in separate modules but it doesn't seem that anyone has tried to do this on a large scale. I tried it (and some other record paradigms including Has), but I don't think it scales. Here's why…
Suppose I have 112 hand-crafted data types in my project (e.g. see attachment 51369.txt[1]), this creates a lot of conflicts in field names and constructor names. For example:
{{{ data Comment = Comment { commentId :: CommentId , commentContent :: Content , commentReviewId :: ReviewId , commentSubmissionId :: SubmissionId , commentConferenceId :: ConferenceId , commentDate :: ISODate , commentReviewerNumber :: Int } deriving (Show) }}}
This is a real type in my project. It has fields like “id”, “content”, “reviewId”, “submissionId”, “date”. There are seven other data types that have a field name “submissionId”. There are 15 with “conferenceId”. There are 7 with “content”. And so on. This is just to demonstrate that field clashes ''do'' occur ''a lot'' in a nontrivial project.
It also demonstrates that if you propose to put each of these 112 types into a separate module, you are having a laugh. I tried this around the 20 type mark and it was, apart from being very slow at compiling, ''very'' tedious to work with. Creating and editing these modules was a distracting and pointless chore.
It ''also'' demonstrated, to me, that qualified imports are horrible when used on a large scale. It happened all the time, that'd I'd import, say, 10 different data types all qualified. Typing map (Foo.id . BarMu.thisField) and foo Bar.Zot{x=1,y=2} becomes tedious and distracting, especially having to add every type module when I want to use a type. And when records use other types in other modules, you have ''a lot'' of redundancy. With the prefixing paradigm I'd write fooId and barMuThisField, which is about as tedious but there is at least less . confusion and no need to make a load of modules and import lines. Perhaps local modules would solve half of this problem. Still have to write “Bar.mu bar” rather than “mu bar”, but it'd be an improvement.
I also have 21 Enum types which often conflict. I end up having to include the name of the type in the constructor, or rewording it awkwardly. I guess I should put these all in separate modules and import qualified, too. Tedious, though. At least in this case languages like C# and Java also require that you type EnumName.EnumValue, so c‘est la vie.
[1]: http://hackage.haskell.org/trac/ghc/attachment/wiki/Records/51369.txt
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

2011/9/15 Greg Weber
Chris, Thank you for the real word experience report. I had assumed (because everyone else told me) that importing qualified would be much, much better than prefixing. I had thought that in your case since you are big on model separation that you would have liked having a separate file for each model to separate out all your model related code with. As a counter point, in all of my (MVC) web application projects, we do have a separate file for each model, and we like this approach. Each file usually contains a lot of "business logic" related to the model- the only relatively empty model files are ones that really represent embedded data. When I use MongoDB (which actually supports embedded data instead of forcing you to create a separate table), I will actually place the embedded models in the same file as the model which includes them.
Ah, this is because my approach to types is to put them in a ProjectName.Types.X module. I /do/ have separate modules for all my models, e.g. $ ls Confy/Model/*.hs Confy/Model/Actions.hs Confy/Model/Driver.hs Confy/Model/Manuscript.hs Confy/Model/Proceedings.hs Confy/Model/SubmissionAuthor.hs Confy/Model/Token.hs Confy/Model/Activity.hs Confy/Model/Fields.hs Confy/Model/Message.hs Confy/Model/ReviewComment.hs Confy/Model/Submission.hs Confy/Model/Track.hs Confy/Model/Author.hs Confy/Model/FormField.hs Confy/Model/Papertype.hs Confy/Model/ReviewerPreference.hs Confy/Model/Tables.hs Confy/Model/User.hs Confy/Model/Conference.hs Confy/Model/Form.hs Confy/Model/Participant.hs Confy/Model/Review.hs Confy/Model/Template.hs Confy/Model/UserMeta.hs Confy/Model/Deadline.hs Confy/Model/LogEntry.hs Confy/Model/Period.hs Confy/Model/Role.hs Confy/Model/TH.hs Confy/Model/Utils.hs I have my HaskellDB types and then I have my normal Haskell types which contain different fields to the database model. But to put the /type/ in the model file itself causes cyclic import problems when I have to start caring about what imports what and then having modules that just contain types, etc. I find this to be quite laborious, I did it at first but it became a hindrance to development practice for me. Have you not found that you have this problem if you put types in the same modules as code in a large project? Examples welcome, too.
After my blog post complaining about records, I had a few people telling me that I can just use existing polymorphism to avoid the name-spacing issue. I collected the approaches here: http://www.yesodweb.com/wiki/record-hacks I didn't think any of those telling me what i should do had actually tried to do this themselves, particularly at any kind of larger scale. I am interested to see if anyone has experience trying this approach, or if you have considered it.
I considered that approach but never tried it, one would probably enlist the help of TemplateHaskell to do that approach properly. Maybe it's not so bad? I suppose I could try making a few branches in my project and try out this approach.

I should be clear that in my counter point I am using Ruby, not Haskell on
those projects. In Ruby one can use a string for the name of a class (which
will be evaluated later) and other general dynamic typing tricks to avoid
cyclical dependencies.
I have worked on one large Yesod project. I felt they were creating
artificially shortened field names in some cases (that I found difficult to
understand/remember) to try and ease the pain of large prefixed record
selectors. However, Yesod does create all the records with prefixes in one
module/file- so all the types are in there. They create a new model file for
each model (conceptually, but not for a model representing simple embedded
data). The model file can import all the record types.
Personally I would prefer to define my type in the model file so I can
quickly see my type with the related code if it were possible, but it seems
that it isn't.
On Thu, Sep 15, 2011 at 8:15 AM, Christopher Done
2011/9/15 Greg Weber
: Chris, Thank you for the real word experience report. I had assumed (because everyone else told me) that importing qualified would be much, much better than prefixing. I had thought that in your case since you are big on model separation that you would have liked having a separate file for each model to separate out all your model related code with. As a counter point, in all of my (MVC) web application projects, we do have a separate file for each model, and we like this approach. Each file usually contains a lot of "business logic" related to the model- the only relatively empty model files are ones that really represent embedded data. When I use MongoDB (which actually supports embedded data instead of forcing you to create a separate table), I will actually place the embedded models in the same file as the model which includes them.
Ah, this is because my approach to types is to put them in a ProjectName.Types.X module. I /do/ have separate modules for all my models, e.g.
$ ls Confy/Model/*.hs Confy/Model/Actions.hs Confy/Model/Driver.hs Confy/Model/Manuscript.hs Confy/Model/Proceedings.hs Confy/Model/SubmissionAuthor.hs Confy/Model/Token.hs Confy/Model/Activity.hs Confy/Model/Fields.hs Confy/Model/Message.hs Confy/Model/ReviewComment.hs Confy/Model/Submission.hs Confy/Model/Track.hs Confy/Model/Author.hs Confy/Model/FormField.hs Confy/Model/Papertype.hs Confy/Model/ReviewerPreference.hs Confy/Model/Tables.hs Confy/Model/User.hs Confy/Model/Conference.hs Confy/Model/Form.hs Confy/Model/Participant.hs Confy/Model/Review.hs Confy/Model/Template.hs Confy/Model/UserMeta.hs Confy/Model/Deadline.hs Confy/Model/LogEntry.hs Confy/Model/Period.hs Confy/Model/Role.hs Confy/Model/TH.hs Confy/Model/Utils.hs
I have my HaskellDB types and then I have my normal Haskell types which contain different fields to the database model.
But to put the /type/ in the model file itself causes cyclic import problems when I have to start caring about what imports what and then having modules that just contain types, etc. I find this to be quite laborious, I did it at first but it became a hindrance to development practice for me. Have you not found that you have this problem if you put types in the same modules as code in a large project? Examples welcome, too.
After my blog post complaining about records, I had a few people telling me that I can just use existing polymorphism to avoid the name-spacing issue. I collected the approaches here: http://www.yesodweb.com/wiki/record-hacks I didn't think any of those telling me what i should do had actually tried to do this themselves, particularly at any kind of larger scale. I am interested to see if anyone has experience trying this approach, or if you have considered it.
I considered that approach but never tried it, one would probably enlist the help of TemplateHaskell to do that approach properly. Maybe it's not so bad? I suppose I could try making a few branches in my project and try out this approach.

2011/9/15 Greg Weber
I should be clear that in my counter point I am using Ruby, not Haskell on those projects. In Ruby one can use a string for the name of a class (which will be evaluated later) and other general dynamic typing tricks to avoid cyclical dependencies.
Ah, okay. Sure, late binding (that's what it's called) makes it convenient.
I have worked on one large Yesod project. I felt they were creating artificially shortened field names in some cases (that I found difficult to understand/remember) to try and ease the pain of large prefixed record selectors.
I can understand that. Accessors like reviewAssignSubmissionId for a ReviewAssign record are pretty tedious but at least I don't have trouble remembering them.
However, Yesod does create all the records with prefixes in one module/file- so all the types are in there. They create a new model file for each model (conceptually, but not for a model representing simple embedded data). The model file can import all the record types.
Right, that's what I do. Types in one big types file, and then the functions for the model in Project.Model.X which imports the types file. This is an internal project, but it will be released as open source in a few months so showing you the haddock output isn't a big deal: http://chrisdone.com/confy-doc/ My militantness regarding adding haddock docs is scant due to deadline pressures as you'd expect, but it's not so bad. E.g. checkout http://chrisdone.com/confy-doc/Confy-Model-Conference.html and it's blatant I'm using a lot of other types. All entities or entity-like things are in: http://chrisdone.com/confy-doc/Confy-Types-Entities.html and enums in http://chrisdone.com/confy-doc/Confy-Types-Enums.html And do not look at http://chrisdone.com/confy-doc/Confy-Model-Tables.html because it is frightening and will make you go bald. If you're already bald, feel free! HaskellDB and its HList-like record system.
Personally I would prefer to define my type in the model file so I can quickly see my type with the related code if it were possible, but it seems that it isn't.
I guess it can be possible with a lot of discipline and patience. Maybe others have done this in large projects and found it not so bad?

It ''also'' demonstrated, to me, that qualified imports are horrible when used on a large scale. It happened all the time, that'd I'd import, say, 10 different data types all qualified. Typing map (Foo.id . BarMu.thisField) and foo Bar.Zot{x=1,y=2} becomes tedious and distracting, especially having to add every type module when I want to use a type. And when records use other types in other modules, you have ''a lot'' of redundancy. With the prefixing paradigm I'd write fooId and barMuThisField, which is about as tedious but there is at least less . confusion and no need to make a load of modules and import lines. Perhaps local modules would solve half of this problem. Still have to write “Bar.mu bar” rather than “mu bar”, but it'd be an improvement.
I disagree about qualified imports, in my experience they're even more useful as the scale increases, because there are many more modules each symbol could come from, and many more name conflicts, and of course there starts to be module name conflicts. I don't find it much of an imposition because I have an automatic tool to add and remove imports when needed, and once the import is already there keyword completion works on it. Grepping for '^data .. =', I have 199 types, of which 97 are probably records, spread over 296 modules. The 97 records are spread over 50 modules, but in practice there are a number with 5-10 and then a long tail with just 1 or 2 each. However I entirely agree a better syntax for records would make programming clearer, more concise, and more fun. I also agree that one module per record is annoying, so I wind up with many records per module, so I have record prefixes anyway. Access is annoying, but not so bad, I think. It's true '(ModuleB.recField2 . ModuleA.recField1) val' is inferior to val.field1.field2, but at least the functions compose and if you make a habit of reducing the dots by defining a few precomposed functions you can cut out a lot of the work of moving a field, or more likely, grouping several fields into a nested record. But the really annoying thing is that modification doesn't compose. Record updates also don't mix well with other update functions. So, just to put give another data point, here's an extreme case: set_track_width view_id tracknum width = do views <- gets views view <- maybe (throw "...") return $ Map.lookup view views track_views <- modify_at "set_track_width" (Block.view_tracks view) tracknum $ \tview -> tview { Block.track_view_width = width } let view' = view { Block.view_tracks = track_views } modify $ \st -> st { state_views = Map.insert view_id view' (state_views st) } I inlined the 'modify_view' function, so this looks even nastier than the real code, but it would be nice to not have to define those helper functions! In an imperative language, this would look something like state.views[view_id].tracks[tracknum].width = width Of course, there's also monadic vs. non-monadic getting its claws in there (and the lack of stack traces, note the explicit passing of the function name). So if I hypothesize a .x syntax that is a modification function, some handwavy two arg forward composition, and remove the monadic part (could get it back in with some fancy combinator footwork), theoretically haskell could get pretty close to the imperative version: let Map.modify k m f = Data.Map.adjust f k m (.views .> Map.modify view_id .> .tracks .> modify_at tracknum .> .width) state width -- or, let set = (...) in state `set` width It's also nice if the field names can be in their own namespace, since it's common to say 'x.y = y'. I'm not saying I think the above would be a good record syntax, just that I think composing with other modify functions is important. Passing to State.modify is another obvious thing to want to do. The above is an extreme case, but I've lost count of the number of times I've typed 'modify $ \st -> st { field = f (field st) }'. I don't even have to think about it anymore. I know that these are the problems that lenses / first class labels libraries aim to solve, so if we're talking only about non-extensible records, I'm curious about what a language extension could provide that a library can't. Simply not being standard and built-in is a big one, but presumably the platform is an answer to that. It would be nice if record updates and access to come with no performance penalty over the existing system, since updating records forms a large part of the runtime of some programs, I'm not sure if the libraries can provide that?

As formulated on the wiki page, the "narrow issue" is a problem without a good solution. The reason the community rejected TDNR is because it's basically polymorphism done wrong. Since we already have polymorphism done right, why would we want it? The right way to deal with records is first to agree a mechanism for writing a context which means "a is a datatype with a field named n of type b" then give the selector n the type "a is a datatype with a field named n of type b" => n :: a -> b There is no reason why this shouldn't be used with the current syntax (although it might clash with more advanced features like first-class labels). Barney.

On Thu, Sep 15, 2011 at 6:03 AM, Barney Hilken
The right way to deal with records is first to agree a mechanism for writing a context which means
"a is a datatype with a field named n of type b"
then give the selector n the type
"a is a datatype with a field named n of type b" => n :: a -> b
There is no reason why this shouldn't be used with the current syntax (although it might clash with more advanced features like first-class labels).
Trex is one existing approach in the Haskell design space http://web.cecs.pdx.edu/~mpj/pubs/polyrec.html http://web.cecs.pdx.edu/~mpj/pubs/lightrec.html /g -- "I’m surprised you haven’t got a little purple space dog, just to ram home what an intergalactic wag you are."

TRex is already mentioned on the wiki as coming at a too high
implementation cost.
2011/9/15 J. Garrett Morris
On Thu, Sep 15, 2011 at 6:03 AM, Barney Hilken
wrote: The right way to deal with records is first to agree a mechanism for writing a context which means
"a is a datatype with a field named n of type b"
then give the selector n the type
"a is a datatype with a field named n of type b" => n :: a -> b
There is no reason why this shouldn't be used with the current syntax (although it might clash with more advanced features like first-class labels).
Trex is one existing approach in the Haskell design space http://web.cecs.pdx.edu/~mpj/pubs/polyrec.html http://web.cecs.pdx.edu/~mpj/pubs/lightrec.html
/g
-- "I’m surprised you haven’t got a little purple space dog, just to ram home what an intergalactic wag you are."
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On 15/09/2011 14:03, Barney Hilken wrote:
As formulated on the wiki page, the "narrow issue" is a problem without a good solution. The reason the community rejected TDNR is because it's basically polymorphism done wrong. Since we already have polymorphism done right, why would we want it?
I think you mean "overloading", not "polymorphism", right? Cheers, Simon

On Thu, Sep 15, 2011 at 08:47:30AM +0000, Simon Peyton-Jones wrote:
Provoked the (very constructive) Yesod blog post on "Limitations of Haskell", and the follow up discussion, I've started a wiki page to collect whatever ideas we have about the name spacing issue for record fields.
http://hackage.haskell.org/trac/ghc/wiki/Records
As Simon M said on Reddit, this is something we'd like to fix; but we need a consensus on how to fix it.
Re TypeDirectedNameResolution, I would actually prefer it if it were less general. i.e. if you were to write x.f then f would be required to be a record selector. Then there's no need for the "If there is exactly one f whose type matches that of x" unpleasantness. Instead, the type of x must be inferred first (without any knowledge about the type of f), and then we know immediately which f is being referred to. Thanks Ian

On 15/09/2011 15:43, Ian Lynagh wrote:
On Thu, Sep 15, 2011 at 08:47:30AM +0000, Simon Peyton-Jones wrote:
Provoked the (very constructive) Yesod blog post on "Limitations of Haskell", and the follow up discussion, I've started a wiki page to collect whatever ideas we have about the name spacing issue for record fields.
http://hackage.haskell.org/trac/ghc/wiki/Records
As Simon M said on Reddit, this is something we'd like to fix; but we need a consensus on how to fix it.
Re TypeDirectedNameResolution, I would actually prefer it if it were less general. i.e. if you were to write x.f then f would be required to be a record selector.
Then there's no need for the "If there is exactly one f whose type matches that of x" unpleasantness. Instead, the type of x must be inferred first (without any knowledge about the type of f), and then we know immediately which f is being referred to.
One benefit of TDNR is to replicate the discoverability of APIs that OO programming has - if x :: Foo then typing "x." in an IDE gives you a list of things you can do with a Foo. (Obviously it's not a complete lis for various reasons, but it does allow the author of Foo and others to design discoverable APIs.) So I think we'd be losing quite a bit to force f to be a record selector. Ganesh

On Thu, Sep 15, 2011 at 10:46 PM, Ganesh Sittampalam
One benefit of TDNR is to replicate the discoverability of APIs that OO programming has - if x :: Foo then typing "x." in an IDE gives you a list of things you can do with a Foo. (Obviously it's not a complete lis for various reasons, but it does allow the author of Foo and others to design discoverable APIs.)
So I think we'd be losing quite a bit to force f to be a record selector.
I'm not sure how to make update work if you allow arbitrary functions on the RHS of a dot. If you expose the Select and Update class, you get some additional flexibility (i.e., the programmer can define new selectors besides field names), but still have a chance to define update (when it's reasonable). /g -- "I’m surprised you haven’t got a little purple space dog, just to ram home what an intergalactic wag you are."

On Fri, Sep 16, 2011 at 06:46:35AM +0100, Ganesh Sittampalam wrote:
On 15/09/2011 15:43, Ian Lynagh wrote:
On Thu, Sep 15, 2011 at 08:47:30AM +0000, Simon Peyton-Jones wrote:
Re TypeDirectedNameResolution, I would actually prefer it if it were less general. i.e. if you were to write x.f then f would be required to be a record selector.
Then there's no need for the "If there is exactly one f whose type matches that of x" unpleasantness.
One benefit of TDNR is to replicate the discoverability of APIs that OO programming has - if x :: Foo then typing "x." in an IDE gives you a list of things you can do with a Foo. (Obviously it's not a complete lis for various reasons, but it does allow the author of Foo and others to design discoverable APIs.)
But add another import, and some of those APIs disappear! And of course, the language doesn't need to support TDNR in order for an IDE to use it (although the juxtaposition application syntax doesn't make the UI easy). Thanks Ian

One benefit of TDNR is to replicate the discoverability of APIs that OO programming has - if x :: Foo then typing "x." in an IDE gives you a list of things you can do with a Foo. (Obviously it's not a complete lis for various reasons, but it does allow the author of Foo and others to design discoverable APIs.)
But add another import, and some of those APIs disappear!
Because of the additional ambiguity? Shouldn't the IDE show all options even if the type checker requires a unique one?
And of course, the language doesn't need to support TDNR in order for an IDE to use it (although the juxtaposition application syntax doesn't make the UI easy).
I'm not sure if I'm saying the same thing as you, here, Ian, but my take is that it be nice for an IDE to solve this in a more general way that works for normal function application as well as "x.f" "backwards" function application. That is, the equivalent of the OOP IDE auto-complete should be to ask "what set of functions apply to this value". The problem is just that the keystrokes order is awkward. Most people typing "f x" probably type 'f' first ;-). But it should be possible to do: " x" left-arrow left-arrow magic-keystroke To get the same set of functions as if you do ("x." magic-keystroke), shouldn't it? (Maybe magic-keystroke inserts an implicit "undefined", type checks, and then figures out the set of functions.) I started playing around with Leksah and scion/emacs (searching for "what's the type of this expr" support) and was a little disappointed that this functionality doesn't seem to exist yet. Or am I wrong and it exists somewhere? Cheers, -Ryan

On 16 September 2011 16:31, Ryan Newton
I started playing around with Leksah and scion/emacs (searching for "what's the type of this expr" support) and was a little disappointed that this functionality doesn't seem to exist yet. Or am I wrong and it exists somewhere?
The problem is when you have incomplete programs. Scion does allow you to ask for the type of a subexpression(1), but it can only do so if the program type checks. In many cases you can get an incomplete program to type check by adding "undefine"s, but that doesn't work in the general case. You may get issues with ambiguous types with no defaulting rules. Ambiguity checking is a separate stage in the type checker (at least it was before 7.0.*) so it's possible in principle to add an option to the GHC API to allow such programs to pass the type checker. I'm not a fan of TDNR because it would make programs even more sensitive to imports than they are now (the current issue is due to type classes and the lack of control about importing them). I also fear that it would interact badly with things an IDE has to do. I may be wrong, though, as some people seem to claim the opposite. I haven't had the chance to think about that fully. Personally, I like the type class + special label type solution best, so far. (1) It's disabled in the current master branch, but it's available in the fork used by EclipseFP and it does indeed work there. -- Push the envelope. Watch it bend.

I personally really like the proposal here: http://research.microsoft.com/en-us/um/people/simonpj/Haskell/records.html The wiki doesn't show any opposition to this system. If Haskell had that record system now I would be very happy and would be fine with leaving other record systems as purely research until this whole research area comes to some decisions.
I believe the way forward is to implement several of the possible systems, and release them for feedback. To get users to actually try out the libraries, I think we need some concrete syntax for constant records, so I suggest we put in a feature request.
It would also be nice if one saintly person could spend the time documenting the available record systems in one document, trying out examples of codebases and collecting surveys on the various systems or something. It's a project in itself. Personally my vote for what it's worth is Worse is Better in this case, and to implement Simon's proposal (not that I think this proposal is Worse, but possibly worse than X-other-really-nice-but-tough-to-decide-on-system). If we still have nothing by this time in six months I'll implement the bloody thing in GHC myself.

Here is a simple concrete proposal: Nonextensible records with polymorphic selectors. ================================================= 1. Introduce a built-in class Label, whose members are strings at the type level. We need a notation for them; I will use double single quotes, so ''string'' is automatically an instance of Label, and you can't define other instances. 2. Define a class (in a library somewhere) class Label n => Contains r n where type field r n :: * select :: r -> n -> field r n update :: r -> n -> field r n -> r 3. Declarations with field labels such as data C = F {l1 :: t1, l2 :: t2} | G {l2 :: t2} are syntactic sugar for data C = F t1 t2 | G t2 instance Contains C ''l1'' where field C ''l1'' = t1 select (F x y) _ = x update (F x y) _ x' = F x' y instance Contains C ''l2'' where field C ''l2'' = t2 select (F x y) _ = y select (G y) _ = y update (F x y) _ y' = F x y' update (G y) _ y' = G y' 4. Selector functions only need to be defined once, however many types they are used in l1 :: Contains r ''l1'' => r -> field r ''l1'' l1 = select r (undef ::''l1'') l2 :: Contains r ''l2'' => r -> field r ''l2'' l2 = select r (undef ::''l2'') 5. Constructors are exactly as before 6. Updates such as r {l1 = x} are syntactic sugar for update r (undef::''l1'') x ===================================================== This has the advantage that the extension to Haskell is fairly small, and it's compatible with existing user code, but if we later decide we want extensible records, we need only add a type function to order Label lexicographically. Barney.

Typos in my last message: the identifier "field" should be "Field" throughout, and "undef" should be "undefined". Here is the corrected version: Nonextensible records with polymorphic selectors. ================================================= 1. Introduce a built-in class Label, whose members are strings at the type level. We need a notation for them; I will use double single quotes, so ''string'' is automatically an instance of Label, and you can't define other instances. 2. Define a class (in a library somewhere) class Label n => Contains r n where type Field r n :: * select :: r -> n -> Field r n update :: r -> n -> Field r n -> r 3. Declarations with field labels such as data C = F {l1 :: t1, l2 :: t2} | G {l2 :: t2} are syntactic sugar for data C = F t1 t2 | G t2 instance Contains C ''l1'' where Field C ''l1'' = t1 select (F x y) _ = x update (F x y) _ x' = F x' y instance Contains C ''l2'' where Field C ''l2'' = t2 select (F x y) _ = y select (G y) _ = y update (F x y) _ y' = F x y' update (G y) _ y' = G y' 4. Selector functions only need to be defined once, however many types they are used in l1 :: Contains r ''l1'' => r -> Field r ''l1'' l1 = select r (undefined ::''l1'') l2 :: Contains r ''l2'' => r -> Field r ''l2'' l2 = select r (undefined ::''l2'') 5. Constructors are exactly as before 6. Updates such as r {l1 = x} are syntactic sugar for update r (undefined::''l1'') x ===================================================== Sorry about that. Barney.

I have added my proposal to the wiki.The only downsides to it that I can see are: 1. If the types can be resolved at compile time, the compiler needs to optimise away the dictionaries, otherwise there will be a performance cost. Since this is always the case for type classes, I assume this is a well-studied problem. 2. The olegites are going to use the Label mechanism for other things, and ask for extra features. What features would be worth implementing? Do we want a member function to return the name as a string? How about a lexicographic ordering type function so we can implement extensible records? Where do we stop? Barney.

| Subject: Re: Records in Haskell | | I have added my proposal to the wiki.The only downsides to it that I can see are: Thanks to Barney for articulating a proposal for records in Haskell. Over various plane rides and ICFP chats I've worked out some more details. It's not as simple as I'd hoped. I'm underwater with stuff at the moment but I did find the time to capture a summary here http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields Please do correct errors, suggest solutions, or explore variants. Simon

I really like Simon PJ's "proposal for records in Haskell". Some
reasons for this are:
- Anonymous record types. For example, an anonymous record type can
easily hold ad-hoc keyword arguments. (Oh, just noticed that said in
the document.)
- To quote the document, "We can express polymorphic update (a
standard awkward case) quite nicely". (If I'm not mistaken (please
tell me if so), OverloadedRecordFields proposal fails here.)
- Nice syntax (in my opinion).
Possible record member set syntax:
let x.k = value in x
Pros:
- No new syntax
- Least Surprise
Cons:
- Verbosity (especially such: (\ x -> let x.k = value in x))
On 20/10/2011, Simon Peyton-Jones
| Subject: Re: Records in Haskell | | I have added my proposal to the wiki.The only downsides to it that I can see are:
Thanks to Barney for articulating a proposal for records in Haskell. Over various plane rides and ICFP chats I've worked out some more details. It's not as simple as I'd hoped.
I'm underwater with stuff at the moment but I did find the time to capture a summary here http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
Please do correct errors, suggest solutions, or explore variants.
Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
-- Matthew Farkas-Dyck

Am Donnerstag, den 20.10.2011, 16:37 +0000 schrieb Simon Peyton-Jones:
| Subject: Re: Records in Haskell | | I have added my proposal to the wiki.The only downsides to it that I can see are:
Thanks to Barney for articulating a proposal for records in Haskell. Over various plane rides and ICFP chats I've worked out some more details. It's not as simple as I'd hoped.
I'm underwater with stuff at the moment but I did find the time to capture a summary here http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
Please do correct errors, suggest solutions, or explore variants.
Simon
Hello, would inclusion of such a record system into GHC mean that plans for first-class labels (http://tinyurl.com/7fppj32) are abandoned? That would be a pity, since first-class labels are very useful to implement record systems that go beyond what the abovementioned record system provides. See, for example, my work on records: http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf http://hackage.haskell.org/package/records Best wishes, Wolfgang

| would inclusion of such a record system into GHC mean that plans for | first-class labels (http://tinyurl.com/7fppj32) are abandoned? That | would be a pity, since first-class labels are very useful to implement | record systems that go beyond what the abovementioned record system | provides. See, for example, my work on records: | http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf | http://hackage.haskell.org/package/records The story is summarised at http://hackage.haskell.org/trac/ghc/wiki/Records First-class labels are one point in the vast swamp of competing and overlapping proposals for records. I think they are summarise here: http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords I am unsure which of this list of proposals you are referring to. The URL you quote is this http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels but it doesn't seem to actually contain a design, merely some options for a design that is implicit. If you do have a design you advocate, it would be good to add it to the list at http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords perhaps explaining which of the other members of the list it subsumes. Because there are so many proposals I have not gone ahead with any of them. The most recent thread, articulated at http://hackage.haskell.org/trac/ghc/wiki/Records is to ask what is the *smallest change* that would solve the *most pressing problem*, namely the inability to use the same field name in different records. First class labels is (I assume) much more ambitious. But maybe not. Anything you can do to bring clarity to the swamp, by editing the above two pages, would be a great service to the community. At the moment, we are stuck in an infinite loop. Simon

I am unsure which of this list of proposals you are referring to. The URL you quote is this http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels
That sounds familiar, I think I wrote that when I was younger;-)
but it doesn't seem to actually contain a design, merely some options for a design that is implicit.
Please note that this particular instance of FirstClassLabels was *not* about record systems themselves (already a hopeless mess of proposals and preferences back then), but about a feature that would help defining record systems *in the language*. So it only outlines designs for shared type-level labels (two of which seemed fairly straightforward, one too open-ended), addressing one specific aspect shared by all advanced record systems (though there are several systems floating around that do not have first-class labels). Some years later, I found a work-around for creating shared type-level labels, outlined in this message, and implemented as Data.Label: Non-atomic "atoms" for type-level programming http://www.haskell.org/pipermail/haskell-cafe/2009-April/059819.html My Haskell community page, http://community.haskell.org/~claus/ near the bottom, has both my Data.Record and my Data.Label sources. I don't get to do much Haskell these days, though, so the code might need tweaking for recent GHC versions. Just in case there are any interested parties who have the time and inclination to take this further;-) Claus

Am Montag, den 07.11.2011, 18:16 +0100 schrieb Claus Reinke:
I am unsure which of this list of proposals you are referring to. The URL you quote is this http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels
That sounds familiar, I think I wrote that when I was younger;-)
but it doesn't seem to actually contain a design, merely some options for a design that is implicit.
Please note that this particular instance of FirstClassLabels was *not* about record systems themselves (already a hopeless mess of proposals and preferences back then), but about a feature that would help defining record systems *in the language*.
Indeed. And I think it is important to make implementing new record systems in the language easier. Each record system built into the language might lack some features that someone wants. So it would be good if one could come up with one’s own record system easily. Best wishes, Wolfgang

On Mon, Nov 7, 2011 at 1:33 PM, Wolfgang Jeltsch wrote: Am Montag, den 07.11.2011, 18:16 +0100 schrieb Claus Reinke: I am unsure which of this list of proposals you are referring to. The
URL you quote is this
http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels That sounds familiar, I think I wrote that when I was younger;-) but it doesn't seem to actually contain a design, merely some options
for a design that is implicit. Please note that this particular instance of FirstClassLabels was *not*
about record systems themselves (already a hopeless mess of proposals
and preferences back then), but about a feature that would help defining
record systems *in the language*. Indeed. And I think it is important to make implementing new record
systems in the language easier. Each record system built into the
language might lack some features that someone wants. So it would be
good if one could come up with one’s own record system easily. On the other hand, I would rather have one half-way decent record system
that doesn't try to do everything than a bunch of ad hoc incompatible ones.
-Edward

Are Records stalled out again? I am perfectly willing to leave the fate of
records up to a willing and capable implementer. That seems much better
than waiting another 5 years for perfection :)
As an intermediate step, is it possible to put a warning in 7.4 when the
dot operator is used without a space so that it can be reserved for usage
with a records solution? Or will the new records solution be turned on by
an extension anyways?
On Mon, Nov 7, 2011 at 10:21 AM, Simon Peyton-Jones
| would inclusion of such a record system into GHC mean that plans for | first-class labels (http://tinyurl.com/7fppj32) are abandoned? That | would be a pity, since first-class labels are very useful to implement | record systems that go beyond what the abovementioned record system | provides. See, for example, my work on records: | < http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf> | http://hackage.haskell.org/package/records
The story is summarised at http://hackage.haskell.org/trac/ghc/wiki/Records
First-class labels are one point in the vast swamp of competing and overlapping proposals for records. I think they are summarise here: http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords I am unsure which of this list of proposals you are referring to. The URL you quote is this http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels but it doesn't seem to actually contain a design, merely some options for a design that is implicit. If you do have a design you advocate, it would be good to add it to the list at http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords perhaps explaining which of the other members of the list it subsumes.
Because there are so many proposals I have not gone ahead with any of them. The most recent thread, articulated at http://hackage.haskell.org/trac/ghc/wiki/Records is to ask what is the *smallest change* that would solve the *most pressing problem*, namely the inability to use the same field name in different records. First class labels is (I assume) much more ambitious. But maybe not.
Anything you can do to bring clarity to the swamp, by editing the above two pages, would be a great service to the community. At the moment, we are stuck in an infinite loop.
Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Another thought: Perhaps bang as record selection operator. It would avoid further corner cases of dot, and it's not unprecedented in Haskell (e.g. Data.Map.!). If one wished to use dot, one could do this: import Prelude hiding ((.)); import Control.Category.Unicode((∘)); (.) = (!);

On Tue, Dec 20, 2011 at 5:57 PM, Matthew Farkas-Dyck
Another thought: Perhaps bang as record selection operator. It would avoid further corner cases of dot, and it's not unprecedented in Haskell (e.g. Data.Map.!).
We already have weird syntax rules for dot, and the proposed change (i.e., dot is an identifier when surrounded with spaces, else it's reserved syntax) actually makes the rules *simpler* in some ways rather than more complex... so why wouldn't we do it that way? The more difficult bit isn't about quirks of syntax, but rather about some significant semantic issues and differing design goals.... should we have a built-in notion of lenses... if so, which formulation... what kinds of punning do we want to preserve, and how deeply should punning go in the semantics, versus be a shallow kind of sugar... how does that interact with the type system... and so on. These are the significant problems.

Fair enough.
On 20/12/2011, Chris Smith
On Tue, Dec 20, 2011 at 5:57 PM, Matthew Farkas-Dyck
wrote: Another thought: Perhaps bang as record selection operator. It would avoid further corner cases of dot, and it's not unprecedented in Haskell (e.g. Data.Map.!).
We already have weird syntax rules for dot, and the proposed change (i.e., dot is an identifier when surrounded with spaces, else it's reserved syntax) actually makes the rules *simpler* in some ways rather than more complex... so why wouldn't we do it that way?
The more difficult bit isn't about quirks of syntax, but rather about some significant semantic issues and differing design goals.... should we have a built-in notion of lenses... if so, which formulation... what kinds of punning do we want to preserve, and how deeply should punning go in the semantics, versus be a shallow kind of sugar... how does that interact with the type system... and so on. These are the significant problems.

Yet another idea: Consider using '\' as record access operator. No conflicts with anything at all, and, moreover, it really looks like hierarchical access. Reminds of filesystems though. Matthew Farkas-Dyck wrote
Another thought: Perhaps bang as record selection operator. It would avoid further corner cases of dot, and it's not unprecedented in Haskell (e.g. Data.Map.!).
-- View this message in context: http://haskell.1045720.n5.nabble.com/Records-in-Haskell-tp4806095p5109437.ht... Sent from the Haskell - Glasgow-haskell-users mailing list archive at Nabble.com.

On 30/12/2011, Andriy Polischuk
Yet another idea: Consider using '\' as record access operator. No conflicts with anything at all, and, moreover, it really looks like hierarchical access. Reminds of filesystems though.
I hope this is a joke.
Matthew Farkas-Dyck wrote
Another thought: Perhaps bang as record selection operator. It would avoid further corner cases of dot, and it's not unprecedented in Haskell (e.g. Data.Map.!).
-- View this message in context: http://haskell.1045720.n5.nabble.com/Records-in-Haskell-tp4806095p5109437.ht... Sent from the Haskell - Glasgow-haskell-users mailing list archive at Nabble.com.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)
Yes, they are stalled again. The "simple solution" turned out to be not simple. I wrote it up at length in
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
There are various unsatisfactory aspects of the proposal, particularly concerning record update. I am not sure how to resolve them.
There was essentially no reaction. As it's quite a lot of work to implement, and no one seemed to care very much, I put it back on the back burner. So that's where it stands.
Meanwhile, AntC has put forth another proposal that I have not had time to look at in detail.
http://www.haskell.org/pipermail/glasgow-haskell-users/2011-December/021298....
What this needs is someone (not me) to lead the discussion and try to make sure it makes progress. For example, does AntC's proposal work? Is it better than the one I articulated? Are any other variants worth considering? Is the gain from overloading record fields worth the pain or design and implementation? Volunteers, stand forth!
Simon
From: Greg Weber [mailto:greg@gregweber.info]
Sent: 09 December 2011 19:38
To: Simon Peyton-Jones
Cc: Wolfgang Jeltsch; glasgow-haskell-users@haskell.org
Subject: Re: Records in Haskell
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)
As an intermediate step, is it possible to put a warning in 7.4 when the dot operator is used without a space so that it can be reserved for usage with a records solution? Or will the new records solution be turned on by an extension anyways?
On Mon, Nov 7, 2011 at 10:21 AM, Simon Peyton-Jones

I am willing to help on this as much as I can. Unfortunately I don't think
you want me to lead the discussion or make decisions on this - many of
these discussions seem over my head. I will continue to study them though
and see if the sink in more.
I do think almost all of these proposals want a dot selector, so it is a
good idea for Haskell to require the normal function (composition) dot to
have spaces around it - should this be brought to the Haskell
Prime committee?
Greg Weber
On Fri, Dec 23, 2011 at 1:52 PM, Simon Peyton-Jones
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
Yes, they are stalled again. The “simple solution” turned out to be not simple. I wrote it up at length in ****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields*** *
There are various unsatisfactory aspects of the proposal, particularly concerning record update. I am not sure how to resolve them. ****
** **
There was essentially no reaction. As it’s quite a lot of work to implement, and no one seemed to care very much, I put it back on the back burner. So that’s where it stands.****
** **
Meanwhile, AntC has put forth another proposal that I have not had time to look at in detail.****
http://www.haskell.org/pipermail/glasgow-haskell-users/2011-December/021298.... ****
** **
*What this needs is someone (not me) to lead the discussion and try to make sure it makes progress*. For example, does AntC’s proposal work? Is it better than the one I articulated? Are any other variants worth considering? Is the gain from overloading record fields worth the pain or design and implementation? Volunteers, stand forth!****
** **
Simon****
** **
** **
*From:* Greg Weber [mailto:greg@gregweber.info] *Sent:* 09 December 2011 19:38 *To:* Simon Peyton-Jones *Cc:* Wolfgang Jeltsch; glasgow-haskell-users@haskell.org
*Subject:* Re: Records in Haskell****
** **
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
As an intermediate step, is it possible to put a warning in 7.4 when the dot operator is used without a space so that it can be reserved for usage with a records solution? Or will the new records solution be turned on by an extension anyways?****
** **
On Mon, Nov 7, 2011 at 10:21 AM, Simon Peyton-Jones
wrote:**** | would inclusion of such a record system into GHC mean that plans for | first-class labels (http://tinyurl.com/7fppj32) are abandoned? That | would be a pity, since first-class labels are very useful to implement | record systems that go beyond what the abovementioned record system | provides. See, for example, my work on records: | < http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf> | http://hackage.haskell.org/package/records****
The story is summarised at http://hackage.haskell.org/trac/ghc/wiki/Records
First-class labels are one point in the vast swamp of competing and overlapping proposals for records. I think they are summarise here: http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords I am unsure which of this list of proposals you are referring to. The URL you quote is this http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels but it doesn't seem to actually contain a design, merely some options for a design that is implicit. If you do have a design you advocate, it would be good to add it to the list at http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords perhaps explaining which of the other members of the list it subsumes.
Because there are so many proposals I have not gone ahead with any of them. The most recent thread, articulated at http://hackage.haskell.org/trac/ghc/wiki/Records is to ask what is the *smallest change* that would solve the *most pressing problem*, namely the inability to use the same field name in different records. First class labels is (I assume) much more ambitious. But maybe not.
Anything you can do to bring clarity to the swamp, by editing the above two pages, would be a great service to the community. At the moment, we are stuck in an infinite loop.
Simon****
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users****
** **

The original goal was to come up with a simple solution. Perhaps that goal
is unattainable, or at least attaining it has much higher cost than we had
hoped. If that is the case we must take a step back and look at changing
how things are done currently. Perhaps the approach that a record label
becomes a global module function is just not feasible.
In the Frege http://code.google.com/p/frege (a Haskell with some
differences implementation on the JVM) language every data type is at the
same time a namespace.
* the function that accesses field x of data type T is T.x
* If a::T then a.x = T.x a
* the function that updates field x of data type T is T.{x=}
* the function that sets field x in a T to 42 is T.{x=42}
* If a::T then a.{x=} and a.{x=42} are valid
* the function that changes field x of a T by applying some function to it
is T.{x <-}
* T.{x?} is a function that returns true iff the argument was constructed
with a data constructor that has field x.
In the Opa language a Module is in fact a Record specially marked as a
module.
What do you think of the Frege system?
On Fri, Dec 23, 2011 at 2:40 PM, Greg Weber
I am willing to help on this as much as I can. Unfortunately I don't think you want me to lead the discussion or make decisions on this - many of these discussions seem over my head. I will continue to study them though and see if the sink in more.
I do think almost all of these proposals want a dot selector, so it is a good idea for Haskell to require the normal function (composition) dot to have spaces around it - should this be brought to the Haskell Prime committee?
Greg Weber
On Fri, Dec 23, 2011 at 1:52 PM, Simon Peyton-Jones
wrote:
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
Yes, they are stalled again. The “simple solution” turned out to be not simple. I wrote it up at length in ****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields** **
There are various unsatisfactory aspects of the proposal, particularly concerning record update. I am not sure how to resolve them. ****
** **
There was essentially no reaction. As it’s quite a lot of work to implement, and no one seemed to care very much, I put it back on the back burner. So that’s where it stands.****
** **
Meanwhile, AntC has put forth another proposal that I have not had time to look at in detail.****
http://www.haskell.org/pipermail/glasgow-haskell-users/2011-December/021298.... ****
** **
*What this needs is someone (not me) to lead the discussion and try to make sure it makes progress*. For example, does AntC’s proposal work? Is it better than the one I articulated? Are any other variants worth considering? Is the gain from overloading record fields worth the pain or design and implementation? Volunteers, stand forth!****
** **
Simon****
** **
** **
*From:* Greg Weber [mailto:greg@gregweber.info] *Sent:* 09 December 2011 19:38 *To:* Simon Peyton-Jones *Cc:* Wolfgang Jeltsch; glasgow-haskell-users@haskell.org
*Subject:* Re: Records in Haskell****
** **
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
As an intermediate step, is it possible to put a warning in 7.4 when the dot operator is used without a space so that it can be reserved for usage with a records solution? Or will the new records solution be turned on by an extension anyways?****
** **
On Mon, Nov 7, 2011 at 10:21 AM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:****
| would inclusion of such a record system into GHC mean that plans for | first-class labels (http://tinyurl.com/7fppj32) are abandoned? That | would be a pity, since first-class labels are very useful to implement | record systems that go beyond what the abovementioned record system | provides. See, for example, my work on records: | < http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf
| http://hackage.haskell.org/package/records****
The story is summarised at http://hackage.haskell.org/trac/ghc/wiki/Records
First-class labels are one point in the vast swamp of competing and overlapping proposals for records. I think they are summarise here: http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords I am unsure which of this list of proposals you are referring to. The URL you quote is this
http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels but it doesn't seem to actually contain a design, merely some options for a design that is implicit. If you do have a design you advocate, it would be good to add it to the list at http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords perhaps explaining which of the other members of the list it subsumes.
Because there are so many proposals I have not gone ahead with any of them. The most recent thread, articulated at http://hackage.haskell.org/trac/ghc/wiki/Records is to ask what is the *smallest change* that would solve the *most pressing problem*, namely the inability to use the same field name in different records. First class labels is (I assume) much more ambitious. But maybe not.
Anything you can do to bring clarity to the swamp, by editing the above two pages, would be a great service to the community. At the moment, we are stuck in an infinite loop.
Simon****
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users****
** **

The semantics of Frege's records are layed out in its manual [1] in the
following sections:
4.2.1 Algebraic Data type Declaration - Constructors with labeled fields
3.2 Primary Expression
The Frege record system explanation is the first one that I could read and
immediately understand (oh, it just creates a namespace!). Perhaps this is
just in part because it was better explained. But I think it is also
because desugaring records to a library is a very complicated proposition,
whereas taking the same library concept but baking it into the compiler and
making simple language modifications is actually much more straightforward
to understand.
Many of the built-in record proposals seem more ambitious (create a new
record from an existing one, generalize in some other direction). More
power or generalization could be very useful, but it can wait for later -
Haskell's records are glaringly bad because they lack name-spacing.
I think one of the problems being faced with improving records is a false
choice between a quick but hacky library desugaring or a major "Extensible"
records built into the compiler. What I am proposing is that (unless
someone proposes a great desugaring solution) we make it the immediate goal
to have records built into the compiler, but done in the simplest (perhaps
least "Extensible") way that just accomplishes name-spacing.
[1] http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf
On Tue, Dec 27, 2011 at 8:32 AM, Greg Weber
The original goal was to come up with a simple solution. Perhaps that goal is unattainable, or at least attaining it has much higher cost than we had hoped. If that is the case we must take a step back and look at changing how things are done currently. Perhaps the approach that a record label becomes a global module function is just not feasible.
In the Frege http://code.google.com/p/frege (a Haskell with some differences implementation on the JVM) language every data type is at the same time a namespace.
* the function that accesses field x of data type T is T.x
* If a::T then a.x = T.x a
* the function that updates field x of data type T is T.{x=}
* the function that sets field x in a T to 42 is T.{x=42}
* If a::T then a.{x=} and a.{x=42} are valid
* the function that changes field x of a T by applying some function to it is T.{x <-}
* T.{x?} is a function that returns true iff the argument was constructed with a data constructor that has field x. In the Opa language a Module is in fact a Record specially marked as a module.
What do you think of the Frege system?
On Fri, Dec 23, 2011 at 2:40 PM, Greg Weber
wrote: I am willing to help on this as much as I can. Unfortunately I don't think you want me to lead the discussion or make decisions on this - many of these discussions seem over my head. I will continue to study them though and see if the sink in more.
I do think almost all of these proposals want a dot selector, so it is a good idea for Haskell to require the normal function (composition) dot to have spaces around it - should this be brought to the Haskell Prime committee?
Greg Weber
On Fri, Dec 23, 2011 at 1:52 PM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
Yes, they are stalled again. The “simple solution” turned out to be not simple. I wrote it up at length in ****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields* ***
There are various unsatisfactory aspects of the proposal, particularly concerning record update. I am not sure how to resolve them. ****
** **
There was essentially no reaction. As it’s quite a lot of work to implement, and no one seemed to care very much, I put it back on the back burner. So that’s where it stands.****
** **
Meanwhile, AntC has put forth another proposal that I have not had time to look at in detail.****
http://www.haskell.org/pipermail/glasgow-haskell-users/2011-December/021298.... ****
** **
*What this needs is someone (not me) to lead the discussion and try to make sure it makes progress*. For example, does AntC’s proposal work? Is it better than the one I articulated? Are any other variants worth considering? Is the gain from overloading record fields worth the pain or design and implementation? Volunteers, stand forth!****
** **
Simon****
** **
** **
*From:* Greg Weber [mailto:greg@gregweber.info] *Sent:* 09 December 2011 19:38 *To:* Simon Peyton-Jones *Cc:* Wolfgang Jeltsch; glasgow-haskell-users@haskell.org
*Subject:* Re: Records in Haskell****
** **
Are Records stalled out again? I am perfectly willing to leave the fate of records up to a willing and capable implementer. That seems much better than waiting another 5 years for perfection :)****
** **
As an intermediate step, is it possible to put a warning in 7.4 when the dot operator is used without a space so that it can be reserved for usage with a records solution? Or will the new records solution be turned on by an extension anyways?****
** **
On Mon, Nov 7, 2011 at 10:21 AM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:****
| would inclusion of such a record system into GHC mean that plans for | first-class labels (http://tinyurl.com/7fppj32) are abandoned? That | would be a pity, since first-class labels are very useful to implement | record systems that go beyond what the abovementioned record system | provides. See, for example, my work on records: | < http://www.informatik.tu-cottbus.de/~jeltsch/research/ppdp-2010-paper.pdf
| http://hackage.haskell.org/package/records****
The story is summarised at http://hackage.haskell.org/trac/ghc/wiki/Records
First-class labels are one point in the vast swamp of competing and overlapping proposals for records. I think they are summarise here: http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords I am unsure which of this list of proposals you are referring to. The URL you quote is this
http://hackage.haskell.org/trac/haskell-prime/wiki/FirstClassLabels but it doesn't seem to actually contain a design, merely some options for a design that is implicit. If you do have a design you advocate, it would be good to add it to the list at http://hackage.haskell.org/trac/ghc/wiki/ExtensibleRecords perhaps explaining which of the other members of the list it subsumes.
Because there are so many proposals I have not gone ahead with any of them. The most recent thread, articulated at http://hackage.haskell.org/trac/ghc/wiki/Records is to ask what is the *smallest change* that would solve the *most pressing problem*, namely the inability to use the same field name in different records. First class labels is (I assume) much more ambitious. But maybe not.
Anything you can do to bring clarity to the swamp, by editing the above two pages, would be a great service to the community. At the moment, we are stuck in an infinite loop.
Simon****
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users****
** **

Quoth Greg Weber
Many of the built-in record proposals seem more ambitious (create a new record from an existing one, generalize in some other direction). More power or generalization could be very useful, but it can wait for later - Haskell's records are glaringly bad because they lack name-spacing.
I think one of the problems being faced with improving records is a false choice between a quick but hacky library desugaring or a major "Extensible" records built into the compiler. What I am proposing is that (unless someone proposes a great desugaring solution) we make it the immediate goal to have records built into the compiler, but done in the simplest (perhaps least "Extensible") way that just accomplishes name-spacing.
It's sure easy to imagine something like that happening, in principle, but ... are you saying that extensibility specifically has been a major issue? Could be, I haven't been paying so much attention. Wouldn't extensibility more or less come along with row polymorphism? I mean, my understanding of the term is that an expression that instantiates a particular record field, can incorporate a record lacking that field, which seems to me to be implicit in row polymorphism anyway. I would think row polymorphism is a must-have. If you're interested in looking at old, Haskell-related record systems, also see O'Haskell. Donn

On Wed, Dec 28, 2011 at 2:12 PM, Donn Cave
Quoth Greg Weber
, ... Many of the built-in record proposals seem more ambitious (create a new record from an existing one, generalize in some other direction). More power or generalization could be very useful, but it can wait for later - Haskell's records are glaringly bad because they lack name-spacing.
I think one of the problems being faced with improving records is a false choice between a quick but hacky library desugaring or a major "Extensible" records built into the compiler. What I am proposing is that (unless someone proposes a great desugaring solution) we make it the immediate goal to have records built into the compiler, but done in the simplest (perhaps least "Extensible") way that just accomplishes name-spacing.
It's sure easy to imagine something like that happening, in principle, but ... are you saying that extensibility specifically has been a major issue? Could be, I haven't been paying so much attention.
Yes, I believe it is common knowledge and stated in many places that the community cannot decide on the best *extensible* record system. http://www.haskell.org/haskellwiki/GHC:FAQ#Extensible_Records
Wouldn't extensibility more or less come along with row polymorphism? I mean, my understanding of the term is that an expression that instantiates a particular record field, can incorporate a record lacking that field, which seems to me to be implicit in row polymorphism anyway. I would think row polymorphism is a must-have.
Perhaps if you want *extensible* records. If you would like to make some progress with records in the near future rather than keeping records in limbo, I think we really need to give up for the moment on any higher form of abstraction than straight-forward name-spacing.
If you're interested in looking at old, Haskell-related record systems, also see O'Haskell.
I am interested in any potential solution. You could link to it on the ExtensibleRecords wiki page and explain it a bit for future reference. O'Haskell seems to be very much concerned with being as extensible as possible - to the point of trying to do OO in Haskell. Greg Weber
Donn
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Quoth Greg Weber
On Wed, Dec 28, 2011 at 2:12 PM, Donn Cave
wrote: ... I would think row polymorphism is a must-have.
Perhaps if you want *extensible* records. If you would like to make some progress with records in the near future rather than keeping records in limbo, I think we really need to give up for the moment on any higher form of abstraction than straight-forward name-spacing.
No, to be clear on that, I haven't given much thought to extensibility per se, I was thinking row polymorphism is a valuable feature on its own, and extensibility just seemed to me to be an implicit side benefit. In principle, a "less is more" approach to language features appeals to me a lot, but not to the point where we just preemptively give up on "any higher form of abstraction". Given the potential for backwards incompatibility, you'd want to have something pretty good to show for it. Donn

On Wed, Dec 28, 2011 at 3:34 PM, Donn Cave
On Wed, Dec 28, 2011 at 2:12 PM, Donn Cave
wrote: ... I would think row polymorphism is a must-have.
Perhaps if you want *extensible* records. If you would like to make some progress with records in the near future rather than keeping records in limbo, I think we really need to give up for the moment on any higher
Quoth Greg Weber
, form of abstraction than straight-forward name-spacing.
No, to be clear on that, I haven't given much thought to extensibility per se, I was thinking row polymorphism is a valuable feature on its own, and extensibility just seemed to me to be an implicit side benefit.
In principle, a "less is more" approach to language features appeals to me a lot, but not to the point where we just preemptively give up on "any higher form of abstraction". Given the potential for backwards incompatibility, you'd want to have something pretty good to show for it.
This is a valid concern. The goal I think we should have is to just to get a release with simple name-spacing resulting in module-like dot notation member selection plus a similar easy syntax for updates. Every extensible records solution I have seen wants this plus some other features. Admittedly I don't understand any of the extensible solutions, so if you can come up with a specific example of backwards incompatibility that would be very useful.
Donn
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On 12/28/11 1:34 PM, Donn Cave wrote:
Quoth Greg Weber
, On Wed, Dec 28, 2011 at 2:12 PM, Donn Cave
wrote: ... I would think row polymorphism is a must-have.
Perhaps if you want *extensible* records. If you would like to make some progress with records in the near future rather than keeping records in limbo, I think we really need to give up for the moment on any higher form of abstraction than straight-forward name-spacing.
No, to be clear on that, I haven't given much thought to extensibility per se, I was thinking row polymorphism is a valuable feature on its own, and extensibility just seemed to me to be an implicit side benefit.
Yes, row polymorphism would still be helpful in lack of extensible records. In particular it allows for easily determining principle (structural) types of records; this is desirable from a generic programming perspective even if records on the whole are not structurally typed. That is, we can distinguish the following types data Foo = MkFoo { x :: T } data Bar = MkBar { x :: T } By considering them to desugar into type Foo = { __type :: Foo , x :: T } constructor MkFoo :: T -> Foo pattern MkFoo :: T -> Pattern type Bar = { __type :: Bar , x :: T } constructor MkBar :: T -> Bar pattern MkBar :: T -> Pattern accessor x :: { x :: T , ... } -> x Of course, an actual implementation needn't come up with a phantom argument like __type in order to nominally distinguish structurally identical types. Rather, the existence of the hack shows that it's doable. -- Live well, ~wren

Greg Weber wrote:> Are Records stalled out again? Simon Peyton-Jones wrote:
Yes, they are stalled again... There was essentially no reaction. As it’s quite a lot of work to implement, and no one seemed to care very much, I put it back on the back burner. So that’s where it stands... What this needs is someone (not me) to lead the discussion and try to make sure it makes progress...
Simon, I think you have misinterpreted the lack of reaction. When I relayed your call for leadership on reddit, there was a huge response: http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... It is quite clear from this that many people, likely a solid majority of everyone active in the Haskell community, considers records to be among the top priorities of things that need to be improved in GHC. The lack of response, I believe, is just a lack of anyone who can cut through all the noise and come up with some practical way to move forward in one of the many possible directions. Thanks, Yitz

| The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions. You're right. But it is very telling that the vast majority of responses on http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed. The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either. Usually I feel I know how to move forward, but here I don't. Simon

Simon, I think you are continuing to move forward admirably on this. Thank
you for contributing my suggestion to the wiki. I just edited the wiki with
some more commentary. In particular I added:
Frege has a detailed explanation of the semantics of its record
implementation, and the language is *very* similar to Haskell. Lets just
start by using Frege's document as the proposal. We can start a new wiki
page as discussions are needed.
As previously stated on this thread, the Frege user manual is available
here:
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type
Declaration - Constructors with labeled fields)
To all those concerned about Records: look at the Frege implementation and
poke holes in it. We only want critiques about
* achieving name-spacing right now
* implementing it in such a way that extensible records could be
implemented in its place in the future, although we will not allow that
discussion to hold up a records implementation now, just possibly modify
things slightly.
Greg Weber
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones
| The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon

Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.
If it's a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under "Better name spacing".
As previously stated on this thread, the Frege user manual is available here:
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)
To all those concerned about Records: look at the Frege implementation and poke holes in it.
Well the most obvious issue is this. 3.2 says
e.m = (T.m e) if the expression e has type t and the type constructor
of t is T and there exists a function T.m
But that innocent-looking statement begs the *entire* question! How do we know if "e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the 'm' field. That is the, qualification with T is compulsory. The trouble with this is that it's *already* possible; simply define suitably named fields
data T = MkE { t_m :: Int, t_n :: Bool }
Here I have prefixed with a (lower case version of) the type name. So we don't seem to be much further ahead.
Maybe one could make it optional if there is no ambiguity, much like Haskell's existing qualified names. But there is considerable ambiguity about whether T.m means
m imported from module T
or
the m record selector of data type T
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership.
Simon
We only want critiques about
* achieving name-spacing right now
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.
Greg Weber
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones

On Dec 31, 2011, at 1:28 PM, Simon Peyton-Jones wrote:
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether. Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write. Cheers, Gershom

Gershom Bazerman wrote:
Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable.
I agree, this would be a great first step forward. Nested modules would be the most helpful. But even just multiple modules per file would be an improvement, and that is Haskell 98 compliant. For past discussion about this idea, see the thread that begins here: http://www.haskell.org/pipermail/haskell-cafe/2008-August/046494.html There is also the issue of how GHC decides which files to open when searching for modules. One easy way to begin would be just to have the caveat that GHC will not find such modules unless it would otherwise look in the file based on the traditional GHC naming conventions. For various reasons including this one, I still think it is a good idea to allow the user to specify a manifest file to GHC instead of relying on GHC to walk the file system itself. See this GHC ticket: http://hackage.haskell.org/trac/ghc/ticket/2550 Thanks, Yitz

On Sun, Jan 01, 2012 at 01:22:31AM -0500, Matthew Farkas-Dyck wrote:
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings
Isn't this possible now with type → kind promotion?
Unfortunately, I believe promotion of built-in types other than lists and tuples has not yet been implemented. In particular, Char cannot yet be promoted. However, there are no theoretical impediments to implementing it that I know of and it should be possible in the future. -Brent

It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether. I think we can do this part without much trouble, once the dust has settled on -XPolyKinds. It certainly fits with all the work we've been doing recently on the kind system. I agree that it's a fairly basic requirement; for example, it's also assumed by http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields Specifically * Allow String as a new kind * Now you can define classes or types with kinds like MyCls :: String -> a -> Constraint T :: String -> * * Provide type-level string literals, so that "foo" :: String Open questions: * Is String (at the kind level) a synonym for [Char]? I'm inclined *not* to do this initially, because it would require us to have promoted character literals too -- and the implementation of record labels as strings of type-level cons-cells is not going to be efficient. * If String is not a kind level synonym for [Char], maybe it should have a different name. For example, "foo" :: Label? Or Atom? After all, if it doesn't behave like a Haskell string it probably should not have the same name. * Are there any operations over Labels? * I don't know exactly what you have in mean by "the ability to reflect the type-level string at the value level". Simon From: Gershom Bazerman [mailto:gershomb@gmail.com] Sent: 31 December 2011 19:12 To: Simon Peyton-Jones Cc: Greg Weber; glasgow-haskell-users@haskell.org Subject: Re: Records in Haskell On Dec 31, 2011, at 1:28 PM, Simon Peyton-Jones wrote: The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either. Usually I feel I know how to move forward, but here I don't. Simon It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether. Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write. Cheers, Gershom

I'm interested in type-level strings myself. I'm using an
approximation in order to enrich the instant-generics-style reflection
of data type declarations with a sensitivity to constructor names. For
example, this lets me automatically convert between many the
similarly-named constructors of related data types (e.g. pipeline of
ASTs in a compiler).
Is there any existing developments regarding type-level strings? I
have (arbitrarily) taken the approach of promoting the cereal library
to the type-level, encoding strings that way, and working from there
(ultimately inspired by Kiselyov and Chan's implicit configurations
paper). It's certainly not perfect, but it's all I need for the
functionality I've been chasing so far.
In regard to Labels versus Atom, etc., in my use case of converting
between similar datatypes, it would be very reasonable to eventually
add/remove prefixes/suffixes from these type-level reifications of
constructor names. If type-level strings are not implemented as lists
of characters, I would still like access to a comparable API. Perhaps
an isomorphism?
Thanks for your time,Nick
PS — I suspect the "reflect to value-level" idea was something along
the lines of automatically providing a function @stringVal :: forall
(a :: Label). a -> String@.
On Mon, Jan 2, 2012 at 6:38 AM, Simon Peyton-Jones
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether.
I think we can do this part without much trouble, once the dust has settled on -XPolyKinds. It certainly fits with all the work we’ve been doing recently on the kind system. I agree that it’s a fairly basic requirement; for example, it’s also assumed by http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
Specifically
· Allow String as a new kind
· Now you can define classes or types with kinds like
MyCls :: String -> a -> Constraint
T :: String -> *
· Provide type-level string literals, so that “foo” :: String
Open questions:
· Is String (at the kind level) a synonym for [Char]? I’m inclined *not* to do this initially, because it would require us to have promoted character literals too -- and the implementation of record labels as strings of type-level cons-cells is not going to be efficient.
· If String is not a kind level synonym for [Char], maybe it should have a different name. For example, “foo” :: Label? Or Atom? After all, if it doesn’t behave like a Haskell string it probably should not have the same name.
· Are there any operations over Labels?
· I don’t know exactly what you have in mean by “the ability to reflect the type-level string at the value level”.
Simon
From: Gershom Bazerman [mailto:gershomb@gmail.com] Sent: 31 December 2011 19:12 To: Simon Peyton-Jones Cc: Greg Weber; glasgow-haskell-users@haskell.org
Subject: Re: Records in Haskell
On Dec 31, 2011, at 1:28 PM, Simon Peyton-Jones wrote:
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether.
Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write.
Cheers,
Gershom
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

| In regard to Labels versus Atom, etc., in my use case of converting | between similar datatypes, it would be very reasonable to eventually | add/remove prefixes/suffixes from these type-level reifications of | constructor names. If type-level strings are not implemented as lists | of characters, I would still like access to a comparable API. Can you be specific? What operations, exactly, do you want? Simon

Disclaimer: this use case for type-level string ops is still hypothetical, so these are predictions. Shooting for the moon, I foresee writing a type-level string similarity metric. In my experience, that would involve nested traversals, sliding of sequence windows, etc. In that case, I would very much like to write the similarity measure algorithm as if the labels were a sequence of characters. The kind in the following signature assumes promoted lists and — though I know it's still up in the air — some sort of promotion of characters as Char.
type LabelToLetters :: Label -> [Char]
Alternatively, the Char kind could simply be Label with an extralingual invariant that it has one "character" in it — though that doesn't smell like Haskell. The resulting kind of LabelToLetters in this case would be (Label -> [Label]). A class of "dumber" algorithms for "comparing" type-level promotions of constructor names might simply attach some prefix or suffix and then test for equality. In that case, I'd just need append.
type LabelAppend :: Label -> Label -> Label
I personally lean towards LabelToLetters, since I predict that any direct interface for labels-as-sequences is going to perpetually be a subset of whatever becomes established as the interface for type-level lists. The symmetric LettersToLabel would be nice for balance, but I don't foresee needing it in this use case, since comparing Labels is more so a consumer of Labels as opposed to a producer. Whatever the interface to Labels, I would need to be able to test type-level characters for equality. While built-in decidable type equality family that returns a type of kind Bool would be fantastic (or preferably GHC ticket 1894), I'm not anticipating that anytime soon. In order to continue using my own library for such a type-level type equality predicate, I need to be able to provide type family instances for the type-level characters.
I need type-level characters to be valid as indices in type family instances.
That's all I foresee. Thanks.
On Tue, Jan 3, 2012 at 3:32 AM, Simon Peyton-Jones
| In regard to Labels versus Atom, etc., in my use case of converting | between similar datatypes, it would be very reasonable to eventually | add/remove prefixes/suffixes from these type-level reifications of | constructor names. If type-level strings are not implemented as lists | of characters, I would still like access to a comparable API.
Can you be specific? What operations, exactly, do you want?
Simon

On Mon, Jan 2, 2012 at 4:38 AM, Simon Peyton-Jones
Open questions:
· Is String (at the kind level) a synonym for [Char]? I’m inclined *not* to do this initially, because it would require us to have promoted character literals too -- and the implementation of record labels as strings of type-level cons-cells is not going to be efficient.
I'd say no, for the simple reason that we have regretted that the value level String type wasn't opaque, preventing us from replacing it with a more efficient implementation. I say make it opaque. -- Johan

On Mon, Jan 2, 2012 at 1:38 PM, Simon Peyton-Jones
· **If String is not a kind level synonym for [Char], maybe it should have a different name. For example, “foo” :: Label? Or Atom?
Or Symbol? The name is inspired by Ruby's :symbol notation. We could even use the same notation (seems unambiguous on the type level, no?). Even if we don't use the notation we could use the name. Sebastian

Hello,
On Mon, Jan 2, 2012 at 4:38 AM, Simon Peyton-Jones
· I don’t know exactly what you have in mean by “the ability to reflect the type-level string at the value level”.
This can be done using singleton types in exactly the same way that it is done on the type-nats branch. It is useful if we want to allow users to define interesting polymorphic functions for values of types with type-level string literals (e.g., in the context of records, this would allow a user to define a custom showing function that can display the record labels). Here is what the type-nat singletons approach might look like for string literals: newtype StringS (s :: String) = StringS String -- Abstract type for singletons (constructor not exported) fromStringS :: StringS s -> String fromStringS (StringS s) = s class StringI s where stringS :: StringS s -- "smart" constructor for StringS values. Users cannot define instances for class "StingI", they are built into GHC. When GHC sees a constraint of the from "StringI X", for a concrete string "X", it discharges it by making a string evidence value containing "X". So, for example, the following would happen on the GHCi prompt:
fromStringS (stringS :: StringS "Hello") "Hello"
The common pattern for using types in this way is something like this: data CustomType (s :: String) = ... tyParamString :: StringI s => CustomType s -> StringS s tyParamString _ = stringS showCustomType :: StringI s => CustomType s -> String showCustomType x = "the type param is " ++ fromStringS (tyParamString x) ++ moreStuff x I hope this helps, -Iavor

On Jan 2, 2012, at 8:05 PM, Iavor Diatchki wrote:
Hello,
On Mon, Jan 2, 2012 at 4:38 AM, Simon Peyton-Jones
wrote: · I don’t know exactly what you have in mean by “the ability to reflect the type-level string at the value level”.
This can be done using singleton types in exactly the same way that it is done on the type-nats branch. It is useful if we want to allow users to define interesting polymorphic functions for values of types with type-level string literals (e.g., in the context of records, this would allow a user to define a custom showing function that can display the record labels). Here is what the type-nat singletons approach might look like for string literals:
newtype StringS (s :: String) = StringS String -- Abstract type for singletons (constructor not exported)
fromStringS :: StringS s -> String fromStringS (StringS s) = s
class StringI s where stringS :: StringS s -- "smart" constructor for StringS values.
Thanks for the clear exposition! This is nearly exactly what I had in mind, and describes precisely one of the use cases I'd imagine. The other tool I could imagine using, although for less common purposes, would be: newtype StringC a = StringC (forall (s :: String). StringS s -> a) runStringC :: String -> StringC a -> a runStringC = compiler magic With type level nats and the like, it's easy enough to write this by hand, and not terribly inefficient. But with type level strings, I'd imagine that it would be nicer to push the work to the compiler. Cheers, Gershom

On 02/01/2012, Simon Peyton-Jones
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether.
I think we can do this part without much trouble, once the dust has settled on -XPolyKinds. It certainly fits with all the work we've been doing recently on the kind system. I agree that it's a fairly basic requirement; for example, it's also assumed by http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
Specifically
* Allow String as a new kind
* Now you can define classes or types with kinds like
MyCls :: String -> a -> Constraint
T :: String -> *
* Provide type-level string literals, so that "foo" :: String
Open questions:
* Is String (at the kind level) a synonym for [Char]? I'm inclined *not* to do this initially, because it would require us to have promoted character literals too -- and the implementation of record labels as strings of type-level cons-cells is not going to be efficient.
* If String is not a kind level synonym for [Char], maybe it should have a different name. For example, "foo" :: Label? Or Atom? After all, if it doesn't behave like a Haskell string it probably should not have the same name.
I agree. In this case, though, I think we ought to allow kind-polymorphic quoted type-level literals, thus: "foobar" :: Label or "foobar" :: String at least.
* Are there any operations over Labels?
* I don't know exactly what you have in mean by "the ability to reflect the type-level string at the value level".
Simon
From: Gershom Bazerman [mailto:gershomb@gmail.com] Sent: 31 December 2011 19:12 To: Simon Peyton-Jones Cc: Greg Weber; glasgow-haskell-users@haskell.org Subject: Re: Records in Haskell
On Dec 31, 2011, at 1:28 PM, Simon Peyton-Jones wrote: The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether.
Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write.
Cheers, Gershom

Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write. I'm agnostic about nested modules. In principle they would be a good thing. However, for the application to records in particular, I'm not sure people would relish saying this module M where module T where data T = MkT { x,y :: Int } module S where data S = MkS { x,y : Int } The trouble is that, as ever, it is easier to say "add nested modules" than it is to say what that means. For example, if I say import qualified M (where M is defined as above), are the data types called M.T, M.S, or M.T.T and M.T.T? What if I import unqualified? Without a design it's hard to debate the pros and cons of different approaches to the record question. If anyone is seriously advocating nested modules, the first step is to work out a concrete design, in detail. Simon From: Gershom Bazerman [mailto:gershomb@gmail.com] Sent: 31 December 2011 19:12 To: Simon Peyton-Jones Cc: Greg Weber; glasgow-haskell-users@haskell.org Subject: Re: Records in Haskell On Dec 31, 2011, at 1:28 PM, Simon Peyton-Jones wrote: The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either. Usually I feel I know how to move forward, but here I don't. Simon It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). Given that, template haskell, and the HList bag of tricks, I'm confident that a fair number of elegant records packages can be crafted. Based on that experience, we can then decide what syntactic sugar would be useful to elide the TH layer altogether. Beyond that, it would really help namespacing in general to appropriately extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". I know that this introduces a few corner cases that need to be thought through -- what happens with overlapping declarations, for example. But I tend to think the path here is relatively straightforward and obvious, and the added expressive power should make namespacing issues much more tractable. Like the type-level strings proposal, this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write. Cheers, Gershom

Simon Peyton-Jones
Beyond that, it would really help namespacing in general to appropriately
much more tractable. [snip] this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write. I’m agnostic about nested modules. In principle they would be a good
extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". [snip] the added expressive power should make namespacing issues thing. However, for the application to records in particular, ... No! no! no! For records _don't_ put records in nested/sub-modules, and _don't_ require them in separate modules (as currently). Here's how ... [There may be other reasons for nested/sub-modules, but records ain't it.] The reason was hinted at way back in Chris Done's attachment to the original Records wiki http://hackage.haskell.org/trac/ghc/wiki/Records "types in a non- trivial project". 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.) In 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: data Customer_NameAddress = Cust_NA { customer_id :: Int, ... } is _not_ declaring customer_id, it's _using_ an already-declared field. (Actually, if you've got any sense, you'll declare: newtype Customer_id = Customer_id Int data ... = { customer_id :: Customer_id, ... } and that takes us to Type-indexed records and then a short hop to anonymous tuples and polymorphic records and tuple extension/concat/merge and ... one day!) The other purpose of the data dictionary is to declare what DBMS's call the 'domain' of the field (Int in the case of customer_id). The terminology's going to get a bit confusing here: Haskell's field name (selector functions) apply to the record as the function's domain, and Int as the result (range). For Haskell's field selectors we might also want to constrain the records they can be used in. (For example they must be 'Persist'able so that we can write them to an external database.) So, to the proposal (I'm assuming http://www.haskell.org/pipermail/glasgow- haskell-users/2011-December/021298.html can be made workable. SPJ has been kind enough to give it a once-over http://www.haskell.org/pipermail/glasgow- haskell-users/2012-January/021744.html, but of course all faults are the author's alone.) Specifically, there's to be a class Has with methods get and set. This is grossly simplified, see the posts): 0. class Has r fld t where -- record r has field fld at type t get :: r -> fld -> t set :: fld -> t -> r -> r' -- update t into r -- the r' is to cater for type-changing updates -- it's actually a type function over r fld t And then: 1. We need -XDisambiguateRecordFields, so that we can talk about specific record types unambiguously(!) 2. We must avoid auto-generating the field selector function from data decls. So that we can free up the namespace and share the field name with other records. (IMHO this should always have been part of -XDisambiguate... I'm not the first to say that.) 3. We need a 'peg' at the type/kind level to drive instance resolution. (This is the `fld' of the Has instance. SPJ's SORF uses a String Kind. I'll use phantom type Proxy_field.) 4. There's to be a new form of declaration: field customer_id -- think data dictionary This is syntactic sugar for: type Proxy_customer_id -- phantom 'peg' customer_id :: (Has r Proxy_customer_id t) => r -> t customer_id r = get r (undefined :: Proxy_customer_id) So we now have a field selector function, similar to H98's but polymorphic. 5. Here's a data decl and its generated Has instance: data Customer_NameAddress = Cust_NA { customer_id :: Int, ... } instance (t ~ Int) => Has Customer_NameAddress Proxy_customer_id t ... in which get/set are defined using the data constructor unambiguously. The (t ~ ...) constraint is to force instance match, followed by type) refinement as a "functional-dependency-like mechanism" [SPJ]. 6. Another data decl using field customer_id generates another Has instance. So customer_id is polymorphic and 'just a function'. So at use sites, we use regular instance resolution, no dodgy semantics. 7. In other modules/namespaces, we can hide the existence and/or representation of fields, using standard export/import controls. We don't have to try to hide the instances (which Haskell don't do). 4b. At step 4. I wasn't telling the whole story. For this application, we want customer_id to be always an Int. We also (let's say) want the record to be Persistable. So the full syntax for the new declaration is: field customer_id :: (Persist r) => r -> Int (The :: type spec is optional.) The decl generates customer_id :: (Persist r, Has r Proxy_customer_id t, t ~ Int ) => r -> t [I'm not sure on that r -> t, perhaps r -> Int is better to help type inference??] Back to modules and namespaces ... Some other module on a distant planet declares a field customer_id. It's using String as the domain (type). (Or perhaps it's using H98-style field naming.) Someone in the asteroid belt builds an application which imports both modules. **Now** we have a name clash: these are different customer_id's. But this is just regular, familiar name clash. We're sweet: the field selector is 'just a function' we can use it qualified. (Also the phantom Proxy_customer_id must be qualified -- not that the end user/programmer needs to know that, not that it matters because it's only a phantom.) Leftovers/problems for the implementer (thanks, Simons ;-): Keyword `field' is probably used all over the place in extant code. (Also `label', and anything else we'd probably like to use.) Somebody please choose another keyword. Let's not put disproportionate effort into lexical syntax. Somewhat off-topic for namespaces, an allowable (optional) syntax for field access is to be dot notation: show r.customer_id -- desugars to: show (customer_id r) How do we disambiguate this when there's differing fields in scope? -- easy: r.My.customer_id -- vs. r.Their.customer_id The syntax rule is that an upper-case to the left of the dot means qualified name, and binds rightwards more tightly. You can use parentheses to override binding. Perhaps the asteroid-dweller wants to create their own record including a customer_id: data Customer_Planet = Cust_Planet { customer_id :: Int, ... } Now we're in trouble: the current syntax for data decl's doesn't contemplate qualified field names. Help!! Perhaps we only allow record decls using field names imported unqualified? A possible refinement: it's daft in the data decl having to spec :: Int all the time, when the field declaration has already said that. Can we default it? Tricky: { customer_id, name, address :: String } looks like it's declaring three fields type String. Syntax for field update: f1 Cust_NA{ customer_id, .. } = Cust_NA{ customer_id = 27, ..} (that is, with explicit data constructors) is regular DisambiguateRecordFields with wildcards and punning. Works as currently (GHC 7.2.). (And in fact the instance definition for set uses this syntax.) Contrast f2 r = r{ customer_id = 27 } (that is, no data constructor prefixing the record update, `r' could be an expression) desugars to a polymorphic call to `set', with normal type inference and instance resolution: f2 r = set (undefined :: Proxy_customer_id) 27 r infer f2 :: r { customer_id :: Num a => a } => r -> r' (The Num a should get refined to Int.) Should get/set have a proxy argument? Like SPJ, I've no strong preference: whatever works. My definition uses proxies (phantoms), because I was working in GHC 7.2.1 without 7.4.1's new PolyKinds. I quickly experimented with avoiding a proxy, but type inference wouldn't play. The end-programmer won't be defining instances, so this could be 'implementation dependent'. Syntactic sugar for `Has', and eliding multiple constraints for the same record. SPJ is absolutely spot-on. Looks very intuitive. I slipped in an example just above.

On 1/02/2012 12:26 AM, AntC wrote:
Simon Peyton-Jones
writes: Beyond that, it would really help namespacing in general to appropriately
much more tractable. [snip] this isn't about implementing records as such -- rather, it's about generally extending the expressive power of the language so that record systems--among other things--are easier to write.
I’m agnostic about nested modules. In principle they would be a good
extend the module system to allow multiple modules to be declared within a single file -- or, better yet, "submodules". [snip] the added expressive power should make namespacing issues thing. However, for the application to records in particular, ...
No! no! no! For records _don't_ put records in nested/sub-modules, and _don't_ require them in separate modules (as currently). Here's how ...
namespace management and record systems are intimately related, but as you assert distinct issues. taking your example of Customer_id, I expressly want to be able to define in the same syntactic module (file). Two records with a field with exactly the same name at two different types, say Customer_id :: Int and Customer_id :: String. As I understand it, your proposal would not enable this. In reality these two different uses of the name Customer_id are unrelated and distinct. Some would argue that therefore they should rightly have distinct names, however that is moot, for the purpose of this discussion lets take this as the objective. There are two roads to travel: Customer_id is one semantic entity (in the sense of an overloaded field, disambiguated by the type system) or Customer_id represents two distinct semantic entities disambiguated syntactically. I for one favor the second approach as it matches my intent, can I say, more faithfully.
[There may be other reasons for nested/sub-modules, but records ain't it.]
as above, however, I believe the best approach is to explore both paths and perhaps extend Haskell in both directions. As in your example the particular construction of which suits, the approach offered in your email better because it matches your intent "more faithfully". It is this ability to match construction with intent that is critical, which alludes to the notion of "the expressivity" of a language.
The reason was hinted at way back in Chris Done's attachment to the original Records wiki http://hackage.haskell.org/trac/ghc/wiki/Records "types in a non- trivial project".
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.)
In 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: data Customer_NameAddress = Cust_NA { customer_id :: Int, ... }
is _not_ declaring customer_id, it's _using_ an already-declared field. (Actually, if you've got any sense, you'll declare: newtype Customer_id = Customer_id Int data ... = { customer_id :: Customer_id, ... } and that takes us to Type-indexed records and then a short hop to anonymous tuples and polymorphic records and tuple extension/concat/merge and ... one day!)
The other purpose of the data dictionary is to declare what DBMS's call the 'domain' of the field (Int in the case of customer_id). The terminology's going to get a bit confusing here: Haskell's field name (selector functions) apply to the record as the function's domain, and Int as the result (range).
For Haskell's field selectors we might also want to constrain the records they can be used in. (For example they must be 'Persist'able so that we can write them to an external database.)
So, to the proposal (I'm assuming http://www.haskell.org/pipermail/glasgow- haskell-users/2011-December/021298.html can be made workable. SPJ has been kind enough to give it a once-over http://www.haskell.org/pipermail/glasgow- haskell-users/2012-January/021744.html, but of course all faults are the author's alone.) Specifically, there's to be a class Has with methods get and set. This is grossly simplified, see the posts):
0. class Has r fld t where -- record r has field fld at type t get :: r -> fld -> t set :: fld -> t -> r -> r' -- update t into r -- the r' is to cater for type-changing updates -- it's actually a type function over r fld t
And then: 1. We need -XDisambiguateRecordFields, so that we can talk about specific record types unambiguously(!) 2. We must avoid auto-generating the field selector function from data decls. So that we can free up the namespace and share the field name with other records. (IMHO this should always have been part of -XDisambiguate... I'm not the first to say that.) 3. We need a 'peg' at the type/kind level to drive instance resolution. (This is the `fld' of the Has instance. SPJ's SORF uses a String Kind. I'll use phantom type Proxy_field.) 4. There's to be a new form of declaration: field customer_id -- think data dictionary This is syntactic sugar for: type Proxy_customer_id -- phantom 'peg' customer_id :: (Has r Proxy_customer_id t) => r -> t customer_id r = get r (undefined :: Proxy_customer_id) So we now have a field selector function, similar to H98's but polymorphic.
5. Here's a data decl and its generated Has instance: data Customer_NameAddress = Cust_NA { customer_id :: Int, ... } instance (t ~ Int) => Has Customer_NameAddress Proxy_customer_id t ... in which get/set are defined using the data constructor unambiguously. The (t ~ ...) constraint is to force instance match, followed by type) refinement as a "functional-dependency-like mechanism" [SPJ]. 6. Another data decl using field customer_id generates another Has instance. So customer_id is polymorphic and 'just a function'. So at use sites, we use regular instance resolution, no dodgy semantics.
7. In other modules/namespaces, we can hide the existence and/or representation of fields, using standard export/import controls. We don't have to try to hide the instances (which Haskell don't do).
4b. At step 4. I wasn't telling the whole story. For this application, we want customer_id to be always an Int. We also (let's say) want the record to be Persistable. So the full syntax for the new declaration is: field customer_id :: (Persist r) => r -> Int (The :: type spec is optional.) The decl generates customer_id :: (Persist r, Has r Proxy_customer_id t, t ~ Int ) => r -> t [I'm not sure on that r -> t, perhaps r -> Int is better to help type inference??]
Back to modules and namespaces ... Some other module on a distant planet declares a field customer_id. It's using String as the domain (type). (Or perhaps it's using H98-style field naming.) Someone in the asteroid belt builds an application which imports both modules.
**Now** we have a name clash: these are different customer_id's. But this is just regular, familiar name clash. We're sweet: the field selector is 'just a function' we can use it qualified. (Also the phantom Proxy_customer_id must be qualified -- not that the end user/programmer needs to know that, not that it matters because it's only a phantom.)
Leftovers/problems for the implementer (thanks, Simons ;-):
Keyword `field' is probably used all over the place in extant code. (Also `label', and anything else we'd probably like to use.) Somebody please choose another keyword. Let's not put disproportionate effort into lexical syntax.
Somewhat off-topic for namespaces, an allowable (optional) syntax for field access is to be dot notation: show r.customer_id -- desugars to: show (customer_id r) How do we disambiguate this when there's differing fields in scope? -- easy: r.My.customer_id -- vs. r.Their.customer_id The syntax rule is that an upper-case to the left of the dot means qualified name, and binds rightwards more tightly. You can use parentheses to override binding.
Perhaps the asteroid-dweller wants to create their own record including a customer_id: data Customer_Planet = Cust_Planet { customer_id :: Int, ... } Now we're in trouble: the current syntax for data decl's doesn't contemplate qualified field names. Help!! Perhaps we only allow record decls using field names imported unqualified?
A possible refinement: it's daft in the data decl having to spec :: Int all the time, when the field declaration has already said that. Can we default it? Tricky: { customer_id, name, address :: String } looks like it's declaring three fields type String.
Syntax for field update: f1 Cust_NA{ customer_id, .. } = Cust_NA{ customer_id = 27, ..} (that is, with explicit data constructors) is regular DisambiguateRecordFields with wildcards and punning. Works as currently (GHC 7.2.). (And in fact the instance definition for set uses this syntax.)
Contrast f2 r = r{ customer_id = 27 } (that is, no data constructor prefixing the record update, `r' could be an expression) desugars to a polymorphic call to `set', with normal type inference and instance resolution: f2 r = set (undefined :: Proxy_customer_id) 27 r infer f2 :: r { customer_id :: Num a => a } => r -> r' (The Num a should get refined to Int.)
Should get/set have a proxy argument? Like SPJ, I've no strong preference: whatever works. My definition uses proxies (phantoms), because I was working in GHC 7.2.1 without 7.4.1's new PolyKinds. I quickly experimented with avoiding a proxy, but type inference wouldn't play. The end-programmer won't be defining instances, so this could be 'implementation dependent'.
Syntactic sugar for `Has', and eliding multiple constraints for the same record. SPJ is absolutely spot-on. Looks very intuitive. I slipped in an example just above.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

John Lask
On 1/02/2012 12:26 AM, AntC wrote:
No! no! no! For records _don't_ put records in nested/sub-modules, and
_don't_
require them in separate modules (as currently). Here's how ...
namespace management and record systems are intimately related, but as you assert distinct issues.
taking your example of Customer_id, I expressly want to be able to define in the same syntactic module (file). Two records with a field with exactly the same name at two different types, say Customer_id :: Int and Customer_id :: String. As I understand it, your proposal would not enable this.
John, you can't do this now. You can't even define in the same module two records with the same name with the _same_ type. [I'd better check first why you've put Customer_id with upper case, that's a type, not a field. Are you sure what you're talking about?] I'd certainly dispute that there's anything sensible in doing that (why? what's the use case?), and I guess my proposal is not making that easy, but it is possible (and in fact no more difficult than a 'fixed' type for a field). You'd go: field customer_id :: r -> t -- which is the same as no type spec at all Then: data CustInt = CustInt { customer_id :: Int, ... } data CustString = CustString { customer_id :: String, ... } It doesn't stop you also doing: data CustBool = CustBool { customer_id :: Bool, ... } (I could contrive that the domain of customer_id is constrained to only Int or String, but why?)
In reality these two different uses of the name Customer_id are unrelated and distinct. Some would argue that therefore they should rightly have distinct names, however that is moot, for the purpose of this discussion lets take this as the objective.
No, I'm not going to take that as an objective. You'd have to provide a much better motivation for wanting to have two identical names in the same scope that are "unrelated and distinct". I'd agree with the 'some' that you should use two different names. Or if this is an 'accidental' clash of names from developers working separately (this is my distant planet example), then the clash is not special for field names, and it's what the module system is for. Use My.customer_id and Their.customer_id.
There are two roads to travel: Customer_id is one semantic entity (in the sense of an overloaded field, disambiguated by the type system) or Customer_id represents two distinct semantic entities disambiguated syntactically. I for one favor the second approach as it matches my intent, can I say, more faithfully.
I'm interested to know how you disambiguate syntactically distinct entities with identical names in the same scope.
[There may be other reasons for nested/sub-modules, but records ain't it.]
as above, however, I believe the best approach is to explore both paths and perhaps extend Haskell in both directions.
No, we can't afford to keep exploring multiple paths. What happens in fact is that it's extremely hard work, there's very few people who can actually implement it, they (frankly) are not very interested when there's so many other exciting possible developments. (Record systems don't count as exciting for programming language research: the ground is already well covered.) There's barely the resourcing to extend Haskell in just one way, and only providing the change is minimal. Haskell 98's record system has been a wart since -- errm -- 1996.
As in your example the particular construction of which suits, the approach offered in your email better because it matches your intent "more faithfully". It is this ability to match construction with intent that is critical, which alludes to the notion of "the expressivity" of a language.
Eh? This sounds like metaphysics.

Simon Peyton-Jones
writes: No! no! no! For records _don't_ put records in nested/sub-modules, and
_don't_ require them in separate modules (as currently). Here's how ...
I've put up my proposal for namespacein Records/fields in Haskell, as an extra page linked from the Wiki. http://hackage.haskell.org/trac/ghc/wiki/Records Apologies for the rough-as-guts editting. AntC
[There may be other reasons for nested/sub-modules, but records ain't it.]
The reason was hinted at way back in Chris Done's attachment to the original Records wiki http://hackage.haskell.org/trac/ghc/wiki/Records "types in a non- trivial project".
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.)

On Sat, Dec 31, 2011 at 3:28 PM, Simon Peyton-Jones
Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.****
** **
If it’s a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under “Better name spacing”.****
** **
As previously stated on this thread, the Frege user manual is available here:****
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf****
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)****
** **
To all those concerned about Records: look at the Frege implementation and poke holes in it. ****
** **
Well the most obvious issue is this. 3.2 says ****
*e*.*m *= (*T*.*m e*) if the expression *e *has type *t *and the type constructor****
of *t *is *T *and there exists a function *T*.*m*
But that innocent-looking statement begs the **entire** question! How do we know if “e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
****
** **
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
More specifically I think of this as TDNR, which instead of the focus of the wiki page of maintaining backwards compatibility and de-surgaring to polymorphic constraints. I had hoped that there were different ideas or at least more flexibility possible for the TDNR implementation.
****
** **
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the ‘m’ field. That is the, qualification with T is compulsory. The trouble with this is that it’s **already** possible; simply define suitably named fields****
data T = MkE { t_m :: Int, t_n :: Bool }****
Here I have prefixed with a (lower case version of) the type name. So we don’t seem to be much further ahead.****
** **
Maybe one could make it optional if there is no ambiguity, much like Haskell’s existing qualified names. But there is considerable ambiguity about whether T.m means ****
m imported from module T****
or****
the m record selector of data type T
If there is ambiguity, we expect the T to be a module. So you would need to refer to Record T's module: OtherModule.T.n or T.T.n Alternatively these conflicts could be compilation errors. Either way programmers are expected to structure their programs to avoid conflicting names, no different then they do now. ****
** **
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership.** **
I am trying to provide as much leadership on this issue as I am capable of. Your critique is very useful in that effort. At this point the Frege proposal without TDNR seems to be a small step forward. We can now define records with clashing fields in the same module. However, without TDNR we don't have convenient access to those fields. I am contacting the Frege author to see if we can get any more insights on implementation details.
Simon****
** **
** **
We only want critiques about****
* achieving name-spacing right now****
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.****
** **
Greg Weber****
** **
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones
wrote:**** | The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.****
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
****
** **

The Frege author does not have a ghc mail list account but gave a more
detailed explanation of how he goes about TDNR for records and how often it
type checks without annotation in practice.
A more general explanation is here:
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders...
He sent a specific response to Simon's mail list message, quoted below:
Simon Peyton-Jones is absolutely correct when he notes:
Well the most obvious issue is this. 3.2 says e.m = (T.m e) if the
expression e has type t and the type constructor of t is T and there exists
a function T.m But that innocent-looking statement begs the *entire*
question! How do we know if "e has type t?
The way it is done in Frege is such that, if you have a function that uses
or updates (nondestructively, of course) a "record" then at least the type
constructor of that record has to be known. This is no different than doing
it explicitly with case constructs, etc., just here you learn the types
from the constructors you write in the patterns.
Hence, it is not so that one can write a function that updates field f to
42 for any record that contains a field f:
foo x = x.{f=42} -- type annotation required for foo or x
In practice this means you'll have to write a type annotation here and
there.
Often, the field access is not the only one that happens to some variable
of record type, or the record is the result of another function
application. In such cases, the type is known.
I estimate that in 2/3 of all cases one does not need to write (T.e x) in
sparsely type annotated code, despite the fact that the frege type checker
has a left to right bias and does not yet attempt to find the type of x in
the code that "follows" the x.e construct (after let unrolling etc.)
I think one could do better and guarantee that, if the type of x is
inferrable at all, then so will be x.e (Still, it must be more than just a
type variable.)
On Sun, Jan 1, 2012 at 2:39 PM, Greg Weber
On Sat, Dec 31, 2011 at 3:28 PM, Simon Peyton-Jones
wrote:
Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.****
** **
If it’s a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under “Better name spacing”.****
** **
As previously stated on this thread, the Frege user manual is available here:****
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf****
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)****
** **
To all those concerned about Records: look at the Frege implementation and poke holes in it. ****
** **
Well the most obvious issue is this. 3.2 says ****
*e*.*m *= (*T*.*m e*) if the expression *e *has type *t *and the type constructor****
of *t *is *T *and there exists a function *T*.*m*
But that innocent-looking statement begs the **entire** question! How do we know if “e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
****
** **
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
More specifically I think of this as TDNR, which instead of the focus of the wiki page of maintaining backwards compatibility and de-surgaring to polymorphic constraints. I had hoped that there were different ideas or at least more flexibility possible for the TDNR implementation.
****
** **
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the ‘m’ field. That is the, qualification with T is compulsory. The trouble with this is that it’s **already** possible; simply define suitably named fields****
data T = MkE { t_m :: Int, t_n :: Bool }****
Here I have prefixed with a (lower case version of) the type name. So we don’t seem to be much further ahead.****
** **
Maybe one could make it optional if there is no ambiguity, much like Haskell’s existing qualified names. But there is considerable ambiguity about whether T.m means ****
m imported from module T****
or****
the m record selector of data type T
If there is ambiguity, we expect the T to be a module. So you would need to refer to Record T's module: OtherModule.T.n or T.T.n Alternatively these conflicts could be compilation errors. Either way programmers are expected to structure their programs to avoid conflicting names, no different then they do now.
****
** **
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership.* ***
I am trying to provide as much leadership on this issue as I am capable of. Your critique is very useful in that effort.
At this point the Frege proposal without TDNR seems to be a small step forward. We can now define records with clashing fields in the same module. However, without TDNR we don't have convenient access to those fields. I am contacting the Frege author to see if we can get any more insights on implementation details.
Simon****
** **
** **
We only want critiques about****
* achieving name-spacing right now****
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.****
** **
Greg Weber****
** **
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:****
| The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.****
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
****
** **

I have updated the wiki - the entry level page [1] compares the different
proposals and points to a more fleshed out explanation of the Frege
proposal [2].
I think I now understand the differences between the existing proposals and
am able to provide leadership to move this forward. Let me summarize the
state of things:
There is a debate over extensible records that we are putting off into the
future. Instead we have 2 proposals to make things better right now:
* an overloaded record fields proposal that still has implementation
concerns
* a name-spacing & simple type resolution proposal that is awaiting your
critique
The Frege language originally had overloaded record fields but then moved
to the latter system. The existing experience of the Frege language is very
fortunate for us as we now have some experience to help inform our own
decision.
Greg Weber
[1] http://hackage.haskell.org/trac/ghc/wiki/Records
[2] http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
On Wed, Jan 4, 2012 at 7:54 AM, Greg Weber
The Frege author does not have a ghc mail list account but gave a more detailed explanation of how he goes about TDNR for records and how often it type checks without annotation in practice.
A more general explanation is here:
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders...
He sent a specific response to Simon's mail list message, quoted below:
Simon Peyton-Jones is absolutely correct when he notes:
Well the most obvious issue is this. 3.2 says e.m = (T.m e) if the expression e has type t and the type constructor of t is T and there exists a function T.m But that innocent-looking statement begs the *entire* question! How do we know if "e has type t?
The way it is done in Frege is such that, if you have a function that uses or updates (nondestructively, of course) a "record" then at least the type constructor of that record has to be known. This is no different than doing it explicitly with case constructs, etc., just here you learn the types from the constructors you write in the patterns.
Hence, it is not so that one can write a function that updates field f to 42 for any record that contains a field f:
foo x = x.{f=42} -- type annotation required for foo or x
In practice this means you'll have to write a type annotation here and there. Often, the field access is not the only one that happens to some variable of record type, or the record is the result of another function application. In such cases, the type is known. I estimate that in 2/3 of all cases one does not need to write (T.e x) in sparsely type annotated code, despite the fact that the frege type checker has a left to right bias and does not yet attempt to find the type of x in the code that "follows" the x.e construct (after let unrolling etc.) I think one could do better and guarantee that, if the type of x is inferrable at all, then so will be x.e (Still, it must be more than just a type variable.)
On Sun, Jan 1, 2012 at 2:39 PM, Greg Weber
wrote: On Sat, Dec 31, 2011 at 3:28 PM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:
Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.****
** **
If it’s a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under “Better name spacing”.****
** **
As previously stated on this thread, the Frege user manual is available here:****
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf*** *
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)****
** **
To all those concerned about Records: look at the Frege implementation and poke holes in it. ****
** **
Well the most obvious issue is this. 3.2 says ****
*e*.*m *= (*T*.*m e*) if the expression *e *has type *t *and the type constructor****
of *t *is *T *and there exists a function *T*.*m*
But that innocent-looking statement begs the **entire** question! How do we know if “e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
****
** **
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with****
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
More specifically I think of this as TDNR, which instead of the focus of the wiki page of maintaining backwards compatibility and de-surgaring to polymorphic constraints. I had hoped that there were different ideas or at least more flexibility possible for the TDNR implementation.
****
** **
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the ‘m’ field. That is the, qualification with T is compulsory. The trouble with this is that it’s **already** possible; simply define suitably named fields****
data T = MkE { t_m :: Int, t_n :: Bool }****
Here I have prefixed with a (lower case version of) the type name. So we don’t seem to be much further ahead.****
** **
Maybe one could make it optional if there is no ambiguity, much like Haskell’s existing qualified names. But there is considerable ambiguity about whether T.m means ****
m imported from module T****
or****
the m record selector of data type T
If there is ambiguity, we expect the T to be a module. So you would need to refer to Record T's module: OtherModule.T.n or T.T.n Alternatively these conflicts could be compilation errors. Either way programmers are expected to structure their programs to avoid conflicting names, no different then they do now.
****
** **
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership. ****
I am trying to provide as much leadership on this issue as I am capable of. Your critique is very useful in that effort.
At this point the Frege proposal without TDNR seems to be a small step forward. We can now define records with clashing fields in the same module. However, without TDNR we don't have convenient access to those fields. I am contacting the Frege author to see if we can get any more insights on implementation details.
Simon****
** **
** **
We only want critiques about****
* achieving name-spacing right now****
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.****
** **
Greg Weber****
** **
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones < simonpj@microsoft.com> wrote:****
| The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.****
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
****
** **

Thank you. I have a few questions/comments.
"The module/record ambiguity is dealt with in Frege by preferring
modules and requiring a module prefix for the record if there is
ambiguity."
I think I see why they do it this way (otherwise you can't refer to a
module if a record by the same name is in scope), but on the other
hand it would seem intuitive to me to choose the more specific thing,
and a record feels more specific than a module. Maybe you could go
that way and just not give your qualified imports the same name as a
record? (Unqualified imports are in practice going to be hierarchical,
and no one's in the habit of typing those out to disambiguate things,
so I don't think it really matters if qualified records shadow them.)
"Expressions of the form x.n: first infer the type of x. If this is
just an unbound type variable (i.e. the type is unknown yet), then
check if n is an overloaded name (i.e. a class operation). [...] Under
no circumstances, however, will the notation x.n contribute in any way
in inferring the type of x, except for the case when n is a class
operation, where an appropriate class constraint is generated."
Is this just a simple translation from x.n to n x? What's the
rationale for allowing the x.n syntax for, in addition to record
fields, class methods specifically, but no other functions?
Later on you write that the names of record fields are only accessible
from the record's namespace and via record syntax, but not from the
global scope. For Haskell I think it would make sense to reverse this
decision. On the one hand, it would keep backwards compatibility; on
the other hand, Haskell code is already written to avoid name clashes
between record fields, so it wouldn't introduce new problems. Large
gain, little pain. You could use the global-namespace function as you
do now, at the risk of ambiguity, or you could use the new record
syntax and avoid it. (If you were to also allow x.n syntax for
arbitrary functions, this could lead to ambiguity again... you could
solve it by preferring a record field belonging to the inferred type
over a function if both are available, but (at least in my current
state of ignorance) I would prefer to just not allow x.n for anything
other than record fields.)
Later on:
"- the function that updates field x of data type T is T.{x=}
- the function that sets field x in a T to 42 is T.{x=42}
- If a::T then a.{x=} and a.{x=42} are valid"
I think this looks considerably ugly. Aren't there better
alternatives? { T.x = }, { T.x = 42 }, { a.x = }, { a.x = 42 } maybe?
(Does this conflict in some unfinesseable way with explicit layout
contexts?)
"the function that changes field x of a T by applying some function to
it is T.{x <-}"
Same comment on syntax applies. I believe this is a new feature? It
would be welcome, albeit the overloading of <- is a bit worrisome
(don't have better ideas at the moment, but I think there was a
thread). I assume T.{x <- f}, a.{x <-}, and a.{x <- f} (whatever the
syntax is) would also be valid, by analogy to the above?
Re: Compatibility with existing records: based on (very) cursory
inspection I don't see an obstacle to making it (near-)fully
compatible - you would just be adding some new syntax, most
significantly x.n. Backwards compatibility is a great advantage, so
why not?
Generalizing the syntax to arbitrary TDNR: I think I'm opposed to
this. The problem is that in existing Haskell the vast majority of
expressions (with the notable (and imho unfortunate) exception of
(>>=)) flow from right to left. Going the other way with record fields
isn't a big problem because it's simple and doesn't even feel like
function application so much as member-selection (like modules), but
if you were to allow any function you would soon end up with lengthy
chains of them which would clash nastily with the surrounding code.
Having to jump back and forth and switch directions while reading is
unpleasant. OO languages have this problem and I don't envy them for
it. And in particular having "a . b" mean "first do b, then do a", but
"a.b" mean "do b to a" would be confusing. (You'd already have this
problem with global namespace record field selectors, but at least
it's localized.)
All of that said, maybe having TDNR with bad syntax is preferable to
not having TDNR at all. Can't it be extended to the existing syntax
(of function application)? Or least some better one, which is ideally
right-to-left? I don't really know the technical details...
Generalized data-namespaces: Also think I'm opposed. This would import
the problem from OO languages where functions written by the module
(class) author get to have a distinguished syntax (be inside the
namespace) over functions by anyone else (which don't).
Another thing that would be nice is lenses to solve the
nested-record-update problem - at least the room to add them later.
Most of the proposed syntax would be unaffected, but you'd need some
syntax for the lens itself... I'm not sure what it might be. Would it
be terrible to have T.x refer to a lens rather than a getter? (I don't
know how you'd refer to the getter then, so probably yeah.) Or maybe {
T.x }, building backwards from { T.x = }?
Another existing language very similar to Haskell whose record system
might be worth evaluating is Disciple: http://disciple.ouroborus.net/.
Unfortunately I couldn't find any specific page it seemed best to link
to.
On Sun, Jan 8, 2012 at 2:40 AM, Greg Weber
I have updated the wiki - the entry level page [1] compares the different proposals and points to a more fleshed out explanation of the Frege proposal [2].
I think I now understand the differences between the existing proposals and am able to provide leadership to move this forward. Let me summarize the state of things: There is a debate over extensible records that we are putting off into the future. Instead we have 2 proposals to make things better right now: * an overloaded record fields proposal that still has implementation concerns * a name-spacing & simple type resolution proposal that is awaiting your critique
The Frege language originally had overloaded record fields but then moved to the latter system. The existing experience of the Frege language is very fortunate for us as we now have some experience to help inform our own decision.
Greg Weber
[1] http://hackage.haskell.org/trac/ghc/wiki/Records [2] http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
On Wed, Jan 4, 2012 at 7:54 AM, Greg Weber
wrote: The Frege author does not have a ghc mail list account but gave a more detailed explanation of how he goes about TDNR for records and how often it type checks without annotation in practice.
A more general explanation is here:
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders...
He sent a specific response to Simon's mail list message, quoted below:
Simon Peyton-Jones is absolutely correct when he notes:
Well the most obvious issue is this. 3.2 says e.m = (T.m e) if the expression e has type t and the type constructor of t is T and there exists a function T.m But that innocent-looking statement begs the *entire* question! How do we know if "e has type t?
The way it is done in Frege is such that, if you have a function that uses or updates (nondestructively, of course) a "record" then at least the type constructor of that record has to be known. This is no different than doing it explicitly with case constructs, etc., just here you learn the types from the constructors you write in the patterns.
Hence, it is not so that one can write a function that updates field f to 42 for any record that contains a field f:
foo x = x.{f=42} -- type annotation required for foo or x
In practice this means you'll have to write a type annotation here and there. Often, the field access is not the only one that happens to some variable of record type, or the record is the result of another function application. In such cases, the type is known. I estimate that in 2/3 of all cases one does not need to write (T.e x) in sparsely type annotated code, despite the fact that the frege type checker has a left to right bias and does not yet attempt to find the type of x in the code that "follows" the x.e construct (after let unrolling etc.) I think one could do better and guarantee that, if the type of x is inferrable at all, then so will be x.e (Still, it must be more than just a type variable.)
On Sun, Jan 1, 2012 at 2:39 PM, Greg Weber
wrote: On Sat, Dec 31, 2011 at 3:28 PM, Simon Peyton-Jones
wrote: Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.
If it’s a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under “Better name spacing”.
As previously stated on this thread, the Frege user manual is available here:
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)
To all those concerned about Records: look at the Frege implementation and poke holes in it.
Well the most obvious issue is this. 3.2 says
e.m = (T.m e) if the expression e has type t and the type constructor
of t is T and there exists a function T.m
But that innocent-looking statement begs the *entire* question! How do we know if “e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
More specifically I think of this as TDNR, which instead of the focus of the wiki page of maintaining backwards compatibility and de-surgaring to polymorphic constraints. I had hoped that there were different ideas or at least more flexibility possible for the TDNR implementation.
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the ‘m’ field. That is the, qualification with T is compulsory. The trouble with this is that it’s *already* possible; simply define suitably named fields
data T = MkE { t_m :: Int, t_n :: Bool }
Here I have prefixed with a (lower case version of) the type name. So we don’t seem to be much further ahead.
Maybe one could make it optional if there is no ambiguity, much like Haskell’s existing qualified names. But there is considerable ambiguity about whether T.m means
m imported from module T
or
the m record selector of data type T
If there is ambiguity, we expect the T to be a module. So you would need to refer to Record T's module: OtherModule.T.n or T.T.n Alternatively these conflicts could be compilation errors. Either way programmers are expected to structure their programs to avoid conflicting names, no different then they do now.
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership.
I am trying to provide as much leadership on this issue as I am capable of. Your critique is very useful in that effort.
At this point the Frege proposal without TDNR seems to be a small step forward. We can now define records with clashing fields in the same module. However, without TDNR we don't have convenient access to those fields. I am contacting the Frege author to see if we can get any more insights on implementation details.
Simon
We only want critiques about
* achieving name-spacing right now
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.
Greg Weber
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones
wrote: | The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
-- Work is punishment for failing to procrastinate effectively.

2012/1/8 Gábor Lehel
Another thing that would be nice is lenses to solve the nested-record-update problem - at least the room to add them later. Most of the proposed syntax would be unaffected, but you'd need some syntax for the lens itself... I'm not sure what it might be. Would it be terrible to have T.x refer to a lens rather than a getter? (I don't know how you'd refer to the getter then, so probably yeah.) Or maybe { T.x }, building backwards from { T.x = }?
Oh, never mind this part, wasn't thinking straight. D'oh.
On Sun, Jan 8, 2012 at 2:40 AM, Greg Weber
wrote: I have updated the wiki - the entry level page [1] compares the different proposals and points to a more fleshed out explanation of the Frege proposal [2].
I think I now understand the differences between the existing proposals and am able to provide leadership to move this forward. Let me summarize the state of things: There is a debate over extensible records that we are putting off into the future. Instead we have 2 proposals to make things better right now: * an overloaded record fields proposal that still has implementation concerns * a name-spacing & simple type resolution proposal that is awaiting your critique
The Frege language originally had overloaded record fields but then moved to the latter system. The existing experience of the Frege language is very fortunate for us as we now have some experience to help inform our own decision.
Greg Weber
[1] http://hackage.haskell.org/trac/ghc/wiki/Records [2] http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
On Wed, Jan 4, 2012 at 7:54 AM, Greg Weber
wrote: The Frege author does not have a ghc mail list account but gave a more detailed explanation of how he goes about TDNR for records and how often it type checks without annotation in practice.
A more general explanation is here:
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders...
He sent a specific response to Simon's mail list message, quoted below:
Simon Peyton-Jones is absolutely correct when he notes:
Well the most obvious issue is this. 3.2 says e.m = (T.m e) if the expression e has type t and the type constructor of t is T and there exists a function T.m But that innocent-looking statement begs the *entire* question! How do we know if "e has type t?
The way it is done in Frege is such that, if you have a function that uses or updates (nondestructively, of course) a "record" then at least the type constructor of that record has to be known. This is no different than doing it explicitly with case constructs, etc., just here you learn the types from the constructors you write in the patterns.
Hence, it is not so that one can write a function that updates field f to 42 for any record that contains a field f:
foo x = x.{f=42} -- type annotation required for foo or x
In practice this means you'll have to write a type annotation here and there. Often, the field access is not the only one that happens to some variable of record type, or the record is the result of another function application. In such cases, the type is known. I estimate that in 2/3 of all cases one does not need to write (T.e x) in sparsely type annotated code, despite the fact that the frege type checker has a left to right bias and does not yet attempt to find the type of x in the code that "follows" the x.e construct (after let unrolling etc.) I think one could do better and guarantee that, if the type of x is inferrable at all, then so will be x.e (Still, it must be more than just a type variable.)
On Sun, Jan 1, 2012 at 2:39 PM, Greg Weber
wrote: On Sat, Dec 31, 2011 at 3:28 PM, Simon Peyton-Jones
wrote: Frege has a detailed explanation of the semantics of its record implementation, and the language is *very* similar to Haskell. Lets just start by using Frege's document as the proposal. We can start a new wiki page as discussions are needed.
If it’s a serious proposal, it needs a page to specify the design. Currently all we have is a paragraph on http://hackage.haskell.org/trac/ghc/wiki/Records, under “Better name spacing”.
As previously stated on this thread, the Frege user manual is available here:
http://code.google.com/p/frege/downloads/detail?name=Language-202.pdf
see Sections 3.2 (primary expressions) and 4.2.1 (Algebraic Data type Declaration - Constructors with labeled fields)
To all those concerned about Records: look at the Frege implementation and poke holes in it.
Well the most obvious issue is this. 3.2 says
e.m = (T.m e) if the expression e has type t and the type constructor
of t is T and there exists a function T.m
But that innocent-looking statement begs the *entire* question! How do we know if “e has type t? This is the route ML takes for arithmetic operators: + means integer plus if the argument is of type Int, float plus if the argument is of type Float, and so on.
Haskell type classes were specifically designed to address this situation. And if you apply type classes to the record situation, I think you end up with
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
More specifically I think of this as TDNR, which instead of the focus of the wiki page of maintaining backwards compatibility and de-surgaring to polymorphic constraints. I had hoped that there were different ideas or at least more flexibility possible for the TDNR implementation.
Well, so maybe we can give up on that. Imagine Frege without the above abbreviation. The basic idea is that field names are rendered unique by pre-pending the module name. As I understand it, to record selection one would then be forced to write (T.m e), to select the ‘m’ field. That is the, qualification with T is compulsory. The trouble with this is that it’s *already* possible; simply define suitably named fields
data T = MkE { t_m :: Int, t_n :: Bool }
Here I have prefixed with a (lower case version of) the type name. So we don’t seem to be much further ahead.
Maybe one could make it optional if there is no ambiguity, much like Haskell’s existing qualified names. But there is considerable ambiguity about whether T.m means
m imported from module T
or
the m record selector of data type T
If there is ambiguity, we expect the T to be a module. So you would need to refer to Record T's module: OtherModule.T.n or T.T.n Alternatively these conflicts could be compilation errors. Either way programmers are expected to structure their programs to avoid conflicting names, no different then they do now.
Perhaps one could make it work out. But before we can talk about it we need to see a design. Which takes us back to the question of leadership.
I am trying to provide as much leadership on this issue as I am capable of. Your critique is very useful in that effort.
At this point the Frege proposal without TDNR seems to be a small step forward. We can now define records with clashing fields in the same module. However, without TDNR we don't have convenient access to those fields. I am contacting the Frege author to see if we can get any more insights on implementation details.
Simon
We only want critiques about
* achieving name-spacing right now
* implementing it in such a way that extensible records could be implemented in its place in the future, although we will not allow that discussion to hold up a records implementation now, just possibly modify things slightly.
Greg Weber
On Thu, Dec 29, 2011 at 2:00 PM, Simon Peyton-Jones
wrote: | The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.
You're right. But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
-- Work is punishment for failing to procrastinate effectively.
-- Work is punishment for failing to procrastinate effectively.

Thanks to Greg for leading the records debate. I apologise that I don't have enough bandwidth to make more than an occasional contribution. Greg's new wiki page, and the discussion so far has clarified my thinking, and this message tries to express that new clarity. I put a conclusion at the end. Simon Overview ~~~~~~~~ It has become clear that there are two elements to pretty much all the proposals we have on the table. Suppose we have two types, 'S' and 'T', both with a field 'f', and you want to select field 'f' from a record 'r'. Somehow you have to disambiguate which 'f' you mean. (Plan A) Disambiguate using qualified names. To select field f, say (S.f r) or (T.f r) respectively. (Plan B) Disambiguate using types. This approach usually implies dot-notation. If (r::S), then (r.f) uses the 'f' from 'S', and similarly if (r::T). Note that * The Frege-derived records proposal (FDR), uses both (A) and (B) http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing * The Simple Overloaded Record Fields (SORF) proposal uses only (B) http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields * The Type Directed Name Resolution proposal (TDNR) uses only (B) http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio... I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambigute common cases. Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated. Here's an example: type family F a b data instance F Int [a] = Mk { f :: Int } g :: F Int b -> () h :: F a [Bool] -> () k x = (g x, x.f, h x) Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended. Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated. Fortunately we know exactly what to do; it is described in some detail in our paper "Modular type inference with local assumptions" http://www.haskell.org/haskellwiki/Simonpj/Talk:OutsideIn The trick is to *defer* all these decisions by generating *type constraints* and solving them later. We express it like this: G, r:t1 |- r.f : t2, (Has t1 "f" t2) This says that if r is in scope with type t1, then (r.f) has type t2, plus the constraint (Has t1 "f" t2), which we read as saying Type t1 must have a field "f" of type t2 We gather up all the constraints and solve them. In solving them we may figure out t1 from some *other* constraint (to the left or right, it's immaterial. That allow us to solve *this* constraint. So it's all quite simple, uniform, and beautiful. It'll fit right into GHC's type-constraint solver. But note what has happened: we have simply re-invented SORF. So the conclusion is this: the only sensible way to implement FDR is using SORF. What about overloading? ~~~~~~~~~~~~~~~~~~~~~~~ A feature of SORF is that you can write functions like this k :: Has r "f" Int => r -> Int k r = r.f + 1 Function 'k' works on any record that has a field 'f'. This may be cool, but it wasn't part of our original goal. And indeed neither FDR nor TDNR offer it. But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does! But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write k :: Num a => a -> a k x = x + x and now it works over *any* Num type. On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is. Is (Plan A) worth it? ~~~~~~~~~~~~~~~~ Once you have (Plan B), and SORF in full glory, plus of course the existing ability to name fields T_f, S_f, if you want, I think it is highly questionable whether we need the additional complexities of (Plan A)? And I do think (Plan A) has lots of additional complexities that we have not begun to explore yet. The data-family thing above is an example, and I can think of some others. But even if it was simple, we still have to ask: does *any* additional complexity give enough payoff, if you already have SORF? I suspect not. Extensions to SORF ~~~~~~~~~~~~~~~~~~ Frege lets you add "virtual fields" to a record type, using an extra RExtension mechanism that I do not yet understand. But SORF lets you do so with no additional mechanism. See "Virtual record selectors" on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields The point is that the existing type-class instance mechanisms do just what we want. Syntax ~~~~~~ The debate on the mailing list has moved sharply towards discussing lexical syntax. I'm not going to engage in that discussion because while it is useful to air opinions, it's very hard to get agreement. But for the record: * I don't mind having Unicode alternatives, but there must be ASCII syntax too * I think we must use ordinary dot for field selection. * I think it's fine to insist on no spaces; we are already doing this for qualified names, as someone pointed out * I think we should not introduce new syntax unless we are absolutely forced into it. Haskell's record update syntax isn't great, but we have it. Conclusion ~~~~~~~~~~ I am driven to the conclusion that SORF is the way to go. - Every other proposal on the table requires SORF (either visibly or invisibly) - When you have SORF, you don't really need anything else The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields a) "Representation hiding" (look for that heading) b) "Record update" (ditto), most especially for records whose fields have polymorphic types If we fix these we can move forward.

On 13/01/2012, Simon Peyton-Jones
Thanks to Greg for leading the records debate. I apologise that I don't have enough bandwidth to make more than an occasional contribution. Greg's new wiki page, and the discussion so far has clarified my thinking, and this message tries to express that new clarity. I put a conclusion at the end.
Simon
Overview ~~~~~~~~ It has become clear that there are two elements to pretty much all the proposals we have on the table. Suppose we have two types, 'S' and 'T', both with a field 'f', and you want to select field 'f' from a record 'r'. Somehow you have to disambiguate which 'f' you mean.
(Plan A) Disambiguate using qualified names. To select field f, say (S.f r) or (T.f r) respectively.
(Plan B) Disambiguate using types. This approach usually implies dot-notation. If (r::S), then (r.f) uses the 'f' from 'S', and similarly if (r::T).
Note that
* The Frege-derived records proposal (FDR), uses both (A) and (B) http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
* The Simple Overloaded Record Fields (SORF) proposal uses only (B) http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
* The Type Directed Name Resolution proposal (TDNR) uses only (B)
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio...
I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambigute common cases.
Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated.
Here's an example:
type family F a b data instance F Int [a] = Mk { f :: Int }
g :: F Int b -> () h :: F a [Bool] -> ()
k x = (g x, x.f, h x)
Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended.
Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families
This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated.
Fortunately we know exactly what to do; it is described in some detail in our paper "Modular type inference with local assumptions" http://www.haskell.org/haskellwiki/Simonpj/Talk:OutsideIn
The trick is to *defer* all these decisions by generating *type constraints* and solving them later. We express it like this:
G, r:t1 |- r.f : t2, (Has t1 "f" t2)
This says that if r is in scope with type t1, then (r.f) has type t2, plus the constraint (Has t1 "f" t2), which we read as saying
Type t1 must have a field "f" of type t2
We gather up all the constraints and solve them. In solving them we may figure out t1 from some *other* constraint (to the left or right, it's immaterial. That allow us to solve *this* constraint.
So it's all quite simple, uniform, and beautiful. It'll fit right into GHC's type-constraint solver.
But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
What about overloading? ~~~~~~~~~~~~~~~~~~~~~~~ A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
Function 'k' works on any record that has a field 'f'. This may be cool, but it wasn't part of our original goal. And indeed neither FDR nor TDNR offer it.
But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
Is (Plan A) worth it? ~~~~~~~~~~~~~~~~
Once you have (Plan B), and SORF in full glory, plus of course the existing ability to name fields T_f, S_f, if you want, I think it is highly questionable whether we need the additional complexities of (Plan A)?
And I do think (Plan A) has lots of additional complexities that we have not begun to explore yet. The data-family thing above is an example, and I can think of some others.
But even if it was simple, we still have to ask: does *any* additional complexity give enough payoff, if you already have SORF? I suspect not.
Extensions to SORF ~~~~~~~~~~~~~~~~~~ Frege lets you add "virtual fields" to a record type, using an extra RExtension mechanism that I do not yet understand. But SORF lets you do so with no additional mechanism. See "Virtual record selectors" on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields The point is that the existing type-class instance mechanisms do just what we want.
Syntax ~~~~~~ The debate on the mailing list has moved sharply towards discussing lexical syntax. I'm not going to engage in that discussion because while it is useful to air opinions, it's very hard to get agreement. But for the record:
* I don't mind having Unicode alternatives, but there must be ASCII syntax too
* I think we must use ordinary dot for field selection.
* I think it's fine to insist on no spaces; we are already doing this for qualified names, as someone pointed out
* I think we should not introduce new syntax unless we are absolutely forced into it. Haskell's record update syntax isn't great, but we have it.
Honestly, I would be glad to find that syntax deprecated/removed. It is awkward and in my opinion not very haskellish. The let-syntax is clean, but quite verbose, and likely suboptimal for other reasons, such as Greg Weber posted to the wiki. Nevertheless, the dot-let-syntax seems (to me at least) the rational convergence of plain let-syntax and the new dot-selection syntax. Perhaps we might: * Define old record update syntax as syntactic sugar for let-syntax. * Allow language extensions, such as Frege-syntax, as sugar for the same.
Conclusion ~~~~~~~~~~ I am driven to the conclusion that SORF is the way to go. - Every other proposal on the table requires SORF (either visibly or invisibly) - When you have SORF, you don't really need anything else
I fully agree. Mind, I'm not a GHC God, but I fully agree nonetheless. A powerful record system (not solely label namespaces) would be a great win for Haskell. This is that record system.
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
b) "Record update" (ditto), most especially for records whose fields have polymorphic types
If we fix these we can move forward.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On Fri, Jan 13, 2012 at 6:52 PM, Simon Peyton-Jones
[... good summary of the issues...] But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
An obvious question at this point: can records have unboxed fields? I'm worried a bit about the kinds that can appear in a has constraint:
A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
I'm thinking out loud about the implementation implications here. -Jan-Willem Maessen

That is a downside the Frege author had - one of the reasons he
abandandoned this style of implementation. It is listed on the wiki.
On Sat, Jan 14, 2012 at 3:28 PM, Jan-Willem Maessen
On Fri, Jan 13, 2012 at 6:52 PM, Simon Peyton-Jones
wrote: [... good summary of the issues...] But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
An obvious question at this point: can records have unboxed fields? I'm worried a bit about the kinds that can appear in a has constraint:
A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
I'm thinking out loud about the implementation implications here.
-Jan-Willem Maessen

| > But note what has happened: we have simply re-invented SORF. So the | > conclusion is this: | > | > the only sensible way to implement FDR is using SORF. | | An obvious question at this point: can records have unboxed fields? | I'm worried a bit about the kinds that can appear in a has constraint: Oh yes! That is indeed an important question, and one I had neglected. All of GHC's abstraction mechanisms only work over *lifted* types (which are all boxed). They don't work for unlifted types (many of which are unboxed, and vary in width). So SORF won't work at all for records with unboxed fields, such as data T = MkT { f :: Int# } But it *will* work for records with boxed fields with an UNPACK pragma data T = MKT { f :: {-# UNPACK #-} Int } I think it's probably an acceptable restriction, but we'd better be aware of it. With FDR (where you can't *abstract* over these constraints) we might be able to allow unboxed field, via some unsavoury ad-hoc cases for Has constraints. But I don't like it. I'll add this to the SORF wiki page. Simon

On Fri, Jan 13, 2012 at 3:52 PM, Simon Peyton-Jones
I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambiguate common cases.
I will try to make the case for (A), just so it has been put on the table. Proposal ========= The proposal is to implement http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing without any of the type resolution parts. I think this approach is dismissed a bit to easily on the wiki page above: "We have name-spaces, but it is hard to see how this is better than the current practice of adding prefixes to record fields: data Record = Record { recordA :: String }" There are (at least) three benefits of using namespaces (e.g. 'Record.a') rather than ad-hoc prefixes (e.g. 'recordA'): * You can use a type synonym to abbreviate the namespace part (as shown on the wiki page.) * If there's no ambiguity you don't need to use a namespace (e.g. you can use 'a' instead of 'Record.a'). * The namespace name is predictable (e.g. <Typename>.<fieldname>) while ad-hoc prefixes tend to use different conventions e.g. the whole record name (e.g. 'recordA') or some abbreviation thereof (e.g. 'rcrdA'.) The main argument for this approach is its simplicity; it's simple to understand for users and (hopefully) simple to implement. Cheers, Johan

On Sat, Jan 14, 2012 at 13:38, Johan Tibell
On Fri, Jan 13, 2012 at 3:52 PM, Simon Peyton-Jones
wrote: I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambiguate common cases.
I will try to make the case for (A), just so it has been put on the table.
I think the point is more that, given (b), (a) is seen to be redundant. Which I don't understand; seems to me that, in the context of (b), it's a way to easily provide more information to the type inferencer (which, given that (b) adds more complexity to the inferencer, looks like a way to control that complexity in practice) without hardcoding a type. -- brandon s allbery allbery.b@gmail.com wandering unix systems administrator (available) (412) 475-9364 vm/sms

On Sat, Jan 14, 2012 at 10:48 AM, Brandon Allbery
On Sat, Jan 14, 2012 at 13:38, Johan Tibell
wrote: On Fri, Jan 13, 2012 at 3:52 PM, Simon Peyton-Jones
wrote: I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambiguate common cases.
I will try to make the case for (A), just so it has been put on the table.
I think the point is more that, given (b), (a) is seen to be redundant.
I agree. The argument for (A) would be to try to avoid the (user*/implementation) complexity of (B). Once you have (B) you (probably) don't want (A).
Which I don't understand; seems to me that, in the context of (b), it's a way to easily provide more information to the type inferencer (which, given that (b) adds more complexity to the inferencer, looks like a way to control that complexity in practice) without hardcoding a type.
I didn't quite follow this part. * Explaining (A) is easy and can be done in a sentence or two e.g. "In addition to module names, type names can now be used to disambiguate record fields. In case of ambiguity between module names and type names, the module name is preferred and the user needs to prefix the type name with a module name to refer to the type name."

2012/1/14 Johan Tibell
On Fri, Jan 13, 2012 at 3:52 PM, Simon Peyton-Jones
wrote: I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambiguate common cases.
I will try to make the case for (A), just so it has been put on the table.
Proposal =========
The proposal is to implement http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing without any of the type resolution parts. I think this approach is dismissed a bit to easily on the wiki page above:
"We have name-spaces, but it is hard to see how this is better than the current practice of adding prefixes to record fields: data Record = Record { recordA :: String }"
There are (at least) three benefits of using namespaces (e.g. 'Record.a') rather than ad-hoc prefixes (e.g. 'recordA'):
* You can use a type synonym to abbreviate the namespace part (as shown on the wiki page.)
* If there's no ambiguity you don't need to use a namespace (e.g. you can use 'a' instead of 'Record.a').
* The namespace name is predictable (e.g. <Typename>.<fieldname>) while ad-hoc prefixes tend to use different conventions e.g. the whole record name (e.g. 'recordA') or some abbreviation thereof (e.g. 'rcrdA'.)
The main argument for this approach is its simplicity; it's simple to understand for users and (hopefully) simple to implement.
At work we have extended the OCaml compiler with the same mechanism, to solve the same problem. I find it quite annoying to use, as you tend to try without a namespace name first, and if you get a compile error you add one. I'd prefer a system which just infers the right selector - less time spent fighting with the compiler! David

Johan, if you are serious, do add a new wiki page to describe the design.
You say it's simple, but I don't think it really is. The whole qualified name story is *already* pretty complicated: see http://ogi.altocumulus.org/~hallgren/hsmod/Description.pdf
Particular issues I can think of immediately:
1. What about data families
data instance T [a] = MkT { x :: Int }
data instance T Bool = T2 { x :: Bool }
What are the qualifies names for the two x's?
2. Module exports. What about
module M( S(..), T(..) ) where
data S = MkS { x :: Int }
data T = MkT { x :: Int }
When you import M, what comes into scope?
3. If you import the record field without the type name (currently possible) does that make the record field impossible to disambiguate? Or is it still ok to disambiguate it with the type name.
My concern would be that it's quite a lot of work to specify and implement; but still might not do the job in the eyes of our users.
Simon
| -----Original Message-----
| From: Johan Tibell [mailto:johan.tibell@gmail.com]
| Sent: 14 January 2012 18:39
| To: Simon Peyton-Jones
| Cc: Greg Weber; ingo.wechsung; glasgow-haskell-users@haskell.org
| Subject: Re: Records in Haskell
|
| On Fri, Jan 13, 2012 at 3:52 PM, Simon Peyton-Jones
|

Simon Peyton-Jones wrote:
Johan, if you are serious, do add a new wiki page to describe the design. You say it's simple, but I don't think it really is.
I'll support Johan by presenting the proposal for A below. I believe that it really is very simple, both in concept and implementation, but correct me if I am wrong. Of course, do not blame any of my errors on Johan, and do not assume that he supports this unless he says so. I'll support the consensus (so far) that B is the nicest. But if we can't get to a design for B that will actually be implemented within a reasonably short period of time, then please, let's try not to get stuck again. If this or some other design for A is indeed deemed to be simple, let's just do that if we can't do B. Perhaps we should set some time limit for B. I propose the following time limit: if a design for A has been tentatively approved and SPJ does not contribute to the discussion about B for one full month, then A automatically gets final approval and will be implemented immediately. [This has the additional advantage of giving SPJ motivation to remain engaged, because he seems to prefer B. :)] Proposal for A: Allow nested modules. A nested module can occur in any position that a data declaration can occur. The syntax of a nested module is the same as the syntax of a conventional module. A nested module must have the same hierarchical name as the module in which it is nested, followed by a single additional component. A name with a single component can be specified in the declaration of a nested module; this is syntactic sugar for the fully qualified name of the enclosing module followed by that single additional component. When a module M.A is directly nested in module M, there is an implied import in the enclosing module M as follows: import qualified M.A as A and an implied import in the nested module M.A as follows: import M These implied imports may optionally be specified explicitly with no effect, or overridden with other explicit imports, similarly to the usual implied import of Prelude. When modules are nested to a depth greater than one, similar implied imports exist for all recursively enclosing and enclosed modules, with the same rules about overriding. If an enclosing module M has an export list, a nested module N at any depth recursively cannot be imported by modules not nested inside M unless N is included in the export list of M. If M does not have an export list, N can be imported by any other module as usual. In every other respect, a nested module declaration has exactly the same effect as any other module declaration. In particular, the behavior of nested modules in the presence of all corner cases such as data families, etc., is specified by this rule. The effect of a nested module on the behavior of ghc --make is left unspecified as of now, until there is feedback from the GHC team. This would probably involve GHC looking for A.B and then A in turn when it fails to find A.B.C. Or perhaps even when A.B.C is found, to identify erroneous duplication. Or GHC could stay pretty much as it is now, relying on the user to ensure that GHC finds the nested module; that would certainly be fine for an initial implementation. Usage example: module Library where import Data.Text (Text) ... type ISBN = Text module Book where import Data.Text (Text) data T = New { name :: Text, iSBN :: ISBN } module Checkout where import Data.Time import qualified Library.Book as Book import qualified Library.Borrower as Borrower data T = New { book :: Book.T, borrower :: Borrower.T, dueDate :: Day } module Borrower where import Data.Text (Text) data T = New { name :: Text, address :: Text } module History where import qualified Library.Borrower as Borrower import qualified Library.Checkout as Checkout data T = New { borrower :: Borrower.T, checkouts :: [Checkout.T] } This makes available in the module Library the record types: Book.T, Checkout.T, Borrower.T, History.T with constructors: Book.New, Checkout.New, Borrower.New, History.New and record accessors: Book.name, Book.iSBN, Checkout.book, Checkout.borrower, Checkout.dueDate, Borrower.name, Borrower.address, History.borrower, History.checkouts I believe this specification should be very simple to implement and describe. There are some obvious shortcomings. But it does provide basic namespacing of records with almost no change to Haskell and GHC. Note also that you need to be careful to avoid mutually recursive imports. That is really more of a limitation of GHC than a limitation of the specification itself. Other compilers might not require that. I'd be happy to hear ideas about how to develop this simple idea further and eliminate some of the shortcomings, on condition that it doesn't lead to further bikeshedding and significant delay. One obvious enhancement would be for the implied import of the enclosing module to include also all names imported into the enclosing module, unlike the usual convention for imports. I'm not sure if there are complications to that though. Thanks, Yitz

Yitz: very helpful. Can you turn your proposal into a Wiki page? It's different to Johan's. Could you add examples? I don't fully understand your design. | [This has the additional advantage of giving SPJ | motivation to remain engaged, because he seems | to prefer B. :)] True: but that's because I think that in the end A will be deemed too clumsy, so we'll end with B anyway. And I'd rather not do both. But I'm quite open to persuasion! | Proposal for A: ... | When a module M.A is directly nested in module M, there is | an implied import in the enclosing module M as follows: | | import qualified M.A as A OK, so consider this: module M where module T where data T = MkT { x :: Int } module S where data S = MkS { x :: Int } So inside M I can refer to T.x and S.x. Fine! What does M export? The current rules say that it cannot export two things both called "x". So this program will be rejected. But that is obviously not what you want. Simon

By the way, thanks to Greg for driving this discussion, please keep up the good work! Simon Peyton-Jones wrote:
Can you turn your proposal into a Wiki page?
OK I'll try to get to that later today.
It's different to Johan's.
Oh? I didn't realize that. OK, I'll look at it more closely. I'm basically just using modules the way they are, changing almost nothing. I wrote:
[This has the additional advantage of giving SPJ motivation to remain engaged, because he seems to prefer B. :)]
True: but that's because I think that in the end A will be deemed too clumsy, so we'll end with B anyway. And I'd rather not do both. But I'm quite open to persuasion!
I agree that B would be great! It just seems harder, and I'm worried that we'll get stuck again. As long as we are moving forward productively towards a solution to B, I'm happy with that. I'm trying to suggest a simple, workable approach to A as a backup.
OK, so consider this: module M where module T where data T = MkT { x :: Int } module S where data S = MkS { x :: Int } So inside M I can refer to T.x and S.x. Fine! What does M export?
As it stands, M exports nothing. In my scheme, nested modules have almost exactly the same meaning as they would have if they were written as separate modules. So far, the only differences are: "module T" is syntactic sugar for "module M.T", and there are implied import statements added to M, T, and S. So an module external to M that wants to use T or S would need to import them explicitly in addition to the import of M. The usual rules would then apply. Based on your comments, I see that another optional enhancement to my proposal could be as follows: Whenever any module E imports M unqualified without an import list, as in: import M then the following implied imports would be added to E: import qualified M.T as T import qualified M.S as S and whenever E imports M qualified without an import list, as in: import qualified M as Q then the following implied imports would be added to E: import qualified M.T as Q.T import qualified M.S as Q.S Similarly, if M also contains more deeply nested modules and E imports M either qualified or unqualified without an import list, the corresponding implied imports of the deeply nested modules would also be added to E. But in fact, this is nothing more than a recursive application of the previous rule. Note that an import statement with an import list will never generate any automatic import of a nested module. I will add this additional enhancement to the wiki page (when I write it). Thanks, Yitz

On Tue, Jan 17, 2012 at 9:59 AM, Yitzchak Gale
By the way, thanks to Greg for driving this discussion, please keep up the good work!
Simon Peyton-Jones wrote:
Can you turn your proposal into a Wiki page?
OK I'll try to get to that later today.
It's different to Johan's.
Oh? I didn't realize that. OK, I'll look at it more closely. I'm basically just using modules the way they are, changing almost nothing.
I wrote:
[This has the additional advantage of giving SPJ motivation to remain engaged, because he seems to prefer B. :)]
True: but that's because I think that in the end A will be deemed too clumsy, so we'll end with B anyway. And I'd rather not do both. But I'm quite open to persuasion!
I agree that B would be great! It just seems harder, and I'm worried that we'll get stuck again. As long as we are moving forward productively towards a solution to B, I'm happy with that. I'm trying to suggest a simple, workable approach to A as a backup.
OK, so consider this: module M where module T where data T = MkT { x :: Int } module S where data S = MkS { x :: Int } So inside M I can refer to T.x and S.x. Fine! What does M export?
As it stands, M exports nothing. In my scheme, nested modules have almost exactly the same meaning as they would have if they were written as separate modules. So far, the only differences are: "module T" is syntactic sugar for "module M.T", and there are implied import statements added to M, T, and S.
So an module external to M that wants to use T or S would need to import them explicitly in addition to the import of M. The usual rules would then apply.
Based on your comments, I see that another optional enhancement to my proposal could be as follows:
Whenever any module E imports M unqualified without an import list, as in:
import M
then the following implied imports would be added to E:
import qualified M.T as T import qualified M.S as S
and whenever E imports M qualified without an import list, as in:
import qualified M as Q
then the following implied imports would be added to E:
import qualified M.T as Q.T import qualified M.S as Q.S
I like this and would like to have it independently of which records system is chosen.
Similarly, if M also contains more deeply nested modules and E imports M either qualified or unqualified without an import list, the corresponding implied imports of the deeply nested modules would also be added to E. But in fact, this is nothing more than a recursive application of the previous rule.
Note that an import statement with an import list will never generate any automatic import of a nested module.
I will add this additional enhancement to the wiki page (when I write it).
Thanks, Yitz
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
-- Work is punishment for failing to procrastinate effectively.

On 01/17/2012 03:59 AM, Yitzchak Gale wrote:
and whenever E imports M qualified without an import list, as in:
import qualified M as Q
then the following implied imports would be added to E:
import qualified M.T as Q.T import qualified M.S as Q.S
Rather, those should be added whether or not M is imported qualified. For example, this is valid Haskell98: import Numeric as Flumeric main = print (Flumeric.showFloat 3.2 "") I like the spirit of this proposal. Does it have the ability yet to make packages with large APIs easier to use? e.g. http://code.haskell.org/gtk2hs/gtk/demo/graphic/Drawing.hs The long lists of import statements can get much worse. The existing remedies (e.g. re-export all the library's identifiers from one module) lose some of the benefits (to readability of code that uses the library) of having a module system. What if export lists like module M (module M.X) where import M.X perhaps... module M (module [qualified] M.X [as X]) where import M.X could trigger similar behavior in importers as nested modules could. What if in module M(....) import .X the "import .X" was sugar for "import M.X as X" (or "as" something else if you wrote an "as" clause explicitly). I think module system improvements like you propose could be a good idea (in general, rather than as a particularly good way to do records, and it shouldn't hold up the type-based records proposal ^_^). ~Isaac

On 2012-01-16 19:16, Yitzchak Gale wrote:
Allow nested modules. [...]
Perhaps Agda's module/record system can provide some inspiration: http://wiki.portal.chalmers.se/agda/pmwiki.php?n=ReferenceManual.Modules http://wiki.portal.chalmers.se/agda/pmwiki.php?n=ReferenceManual.Records (I don't think the wiki pages above are entirely complete/up to date, but for the purposes of this discussion they should do.) -- /NAD

Thank you Nils for those excellent links.
Yitz would like Agda's module/record setup which allows for multiple
modules in a single file.
Records are yet another local module.
The neat thing is, that like any module you can "open" it, or put it
into the scope of your current module for convenient use that avoids
the needs for qualifiers (see "Record opening example").
Johan, I think Agda's use of records might also be what you are getting at.
On Tue, Jan 17, 2012 at 7:10 AM, Nils Anders Danielsson
On 2012-01-16 19:16, Yitzchak Gale wrote:
Allow nested modules. [...]
Perhaps Agda's module/record system can provide some inspiration:
http://wiki.portal.chalmers.se/agda/pmwiki.php?n=ReferenceManual.Modules http://wiki.portal.chalmers.se/agda/pmwiki.php?n=ReferenceManual.Records
(I don't think the wiki pages above are entirely complete/up to date, but for the purposes of this discussion they should do.)
-- /NAD
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

On Mon, Jan 16, 2012 at 2:32 AM, Simon Peyton-Jones
Johan, if you are serious, do add a new wiki page to describe the design.
I haven't thought trough the issues enough to describe the design just yet. At the moment I prefer to discuss a little more to gain some more clarity/insight.
You say it's simple, but I don't think it really is. The whole qualified name story is *already* pretty complicated: see http://ogi.altocumulus.org/~hallgren/hsmod/Description.pdf
I will definitely read this.
Particular issues I can think of immediately:
1. What about data families
data instance T [a] = MkT { x :: Int } data instance T Bool = T2 { x :: Bool }
I haven't really considered data families (and probably a bunch of other extensions.) Since data families are type functions it seems to me that to resolve 'x' we would also consider its type. At that point you might say we should just do (B).
What are the qualifies names for the two x's?
2. Module exports. What about
module M( S(..), T(..) ) where data S = MkS { x :: Int } data T = MkT { x :: Int }
The general idea is to treat a data type declaration data T = C { x :: t } as a declaration containing a local module: data T = C { module T { x :: t } } This is not unlike how OO languages do namespacing for classes and modules e.g. in Java package foo; class C { int x; // Static variable } you can then say foo.C.x.
When you import M, what comes into scope?
The qualified names S.x and T.x. This might not play well with our module system though. We generally don't allow you to export or import name*spaces* e.g. module M where import Data -- contains e.g. Data.Text, Data.Map, etc f = Text.concat In other words: our hierarchical modules system isn't very hierarchical in that you can only mention leaf nodes. We could (I think) get rid of all the dots in our modules names without changing the semantics.
3. If you import the record field without the type name (currently possible) does that make the record field impossible to disambiguate? Or is it still ok to disambiguate it with the type name.
I'd say no to the latter. It should probably be an import error. I'm not sure how we can maintain backwards compatibility (i.e. also exporting record fields as top-level identifiers, given you module example above) under this proposal. Cheers, Johan

On Wed, Jan 18, 2012 at 9:43 AM, Johan Tibell
On Mon, Jan 16, 2012 at 2:32 AM, Simon Peyton-Jones
wrote: Johan, if you are serious, do add a new wiki page to describe the design.
I haven't thought trough the issues enough to describe the design just yet. At the moment I prefer to discuss a little more to gain some more clarity/insight.
Btw, definitely don't stop the presses waiting for me to find some time to think this through. If you see a clear path to implement some proposal please go ahead. I just wanted to point out we shouldn't dismiss the namespace system as a way to solve a namespacing issue (i.e. which of those two x:es did you mean)! Cheers, Johan

On Sat, Jan 14, 2012 at 12:52 AM, Simon Peyton-Jones
Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated.
Here's an example:
type family F a b data instance F Int [a] = Mk { f :: Int }
g :: F Int b -> () h :: F a [Bool] -> ()
k x = (g x, x.f, h x)
Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended.
Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families
This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated.
I rarely use Haskell's complex type machinery (and must admit to very little understanding of it). So before dismissing this as difficult to resolve in the face of a complex type system, For the complex type system I would like to figure out. * I am assuming that it is always possible to add a type annotation. Is there extra complexity to the annotation, or can we just annotate the record or top-level function? * which parts of Haskell's complex type sytem will require type annotations for Frege-style records. * does using a library that uses complex machinery under the hood but tries to limit the complexity of types exposed to the user end up infecting the user's code with respect to difficulty in resolving records. What makes the Haskell's type system great is first and foremost typeclasses. And this is as complex as the vast majority of code gets. It doesn't sound that bad to have an implementation that works very well for the vast majority of code but requires using type annotations or normal verbose name-spacing for those few parts that are already using verbose name spacing.
- When you have SORF, you don't really need anything else
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
b) "Record update" (ditto), most especially for records whose fields have polymorphic types
If we fix these we can move forward.
So essentially records are stalled again :( I will keep trying to see if I can figure something out.

On Sun, Jan 15, 2012 at 01:38:20PM +0100, Greg Weber wrote:
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
How about http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop... for resolving this? Thanks Ian

On 15/01/2012, Ian Lynagh
On Sun, Jan 15, 2012 at 01:38:20PM +0100, Greg Weber wrote:
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
How about
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop...
for resolving this?
(I also posted a like comment to the wiki.) In my opinion, this is ugly, since the selector can be either a type name or a label and the semantics are nonsame.
From section "Representation hiding": "it would require a new implementation mechanism [sic] to record exactly which instances were exported from a module" Win.
Thanks Ian
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
Cheers, MFD

On 13/01/2012, Simon Peyton-Jones
Thanks to Greg for leading the records debate. I apologise that I don't have enough bandwidth to make more than an occasional contribution. Greg's new wiki page, and the discussion so far has clarified my thinking, and this message tries to express that new clarity. I put a conclusion at the end.
Simon
Overview ~~~~~~~~ It has become clear that there are two elements to pretty much all the proposals we have on the table. Suppose we have two types, 'S' and 'T', both with a field 'f', and you want to select field 'f' from a record 'r'. Somehow you have to disambiguate which 'f' you mean.
(Plan A) Disambiguate using qualified names. To select field f, say (S.f r) or (T.f r) respectively.
(Plan B) Disambiguate using types. This approach usually implies dot-notation. If (r::S), then (r.f) uses the 'f' from 'S', and similarly if (r::T).
Note that
* The Frege-derived records proposal (FDR), uses both (A) and (B) http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
* The Simple Overloaded Record Fields (SORF) proposal uses only (B) http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
* The Type Directed Name Resolution proposal (TDNR) uses only (B)
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio...
I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambigute common cases.
Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated.
Here's an example:
type family F a b data instance F Int [a] = Mk { f :: Int }
g :: F Int b -> () h :: F a [Bool] -> ()
k x = (g x, x.f, h x)
Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended.
Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families
This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated.
Fortunately we know exactly what to do; it is described in some detail in our paper "Modular type inference with local assumptions" http://www.haskell.org/haskellwiki/Simonpj/Talk:OutsideIn
The trick is to *defer* all these decisions by generating *type constraints* and solving them later. We express it like this:
G, r:t1 |- r.f : t2, (Has t1 "f" t2)
This says that if r is in scope with type t1, then (r.f) has type t2, plus the constraint (Has t1 "f" t2), which we read as saying
Type t1 must have a field "f" of type t2
We gather up all the constraints and solve them. In solving them we may figure out t1 from some *other* constraint (to the left or right, it's immaterial. That allow us to solve *this* constraint.
So it's all quite simple, uniform, and beautiful. It'll fit right into GHC's type-constraint solver.
But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
What about overloading? ~~~~~~~~~~~~~~~~~~~~~~~ A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
Function 'k' works on any record that has a field 'f'. This may be cool, but it wasn't part of our original goal. And indeed neither FDR nor TDNR offer it.
But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
Is (Plan A) worth it? ~~~~~~~~~~~~~~~~
Once you have (Plan B), and SORF in full glory, plus of course the existing ability to name fields T_f, S_f, if you want, I think it is highly questionable whether we need the additional complexities of (Plan A)?
And I do think (Plan A) has lots of additional complexities that we have not begun to explore yet. The data-family thing above is an example, and I can think of some others.
But even if it was simple, we still have to ask: does *any* additional complexity give enough payoff, if you already have SORF? I suspect not.
Extensions to SORF ~~~~~~~~~~~~~~~~~~ Frege lets you add "virtual fields" to a record type, using an extra RExtension mechanism that I do not yet understand. But SORF lets you do so with no additional mechanism. See "Virtual record selectors" on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields The point is that the existing type-class instance mechanisms do just what we want.
Syntax ~~~~~~ The debate on the mailing list has moved sharply towards discussing lexical syntax. I'm not going to engage in that discussion because while it is useful to air opinions, it's very hard to get agreement. But for the record:
* I don't mind having Unicode alternatives, but there must be ASCII syntax too
* I think we must use ordinary dot for field selection.
* I think it's fine to insist on no spaces; we are already doing this for qualified names, as someone pointed out
* I think we should not introduce new syntax unless we are absolutely forced into it. Haskell's record update syntax isn't great, but we have it.
Conclusion ~~~~~~~~~~ I am driven to the conclusion that SORF is the way to go. - Every other proposal on the table requires SORF (either visibly or invisibly) - When you have SORF, you don't really need anything else
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
b) "Record update" (ditto), most especially for records whose fields have polymorphic types
I posted to the wiki a possible solution to (b): http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Alte...
If we fix these we can move forward.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Sorry to pick on your post in particular Matthew, but I have been seeing a lot of this on the Haskell lists lately. I find it completely unreasonable for a reply to a very long post to quote the entire text, only to add a single line at the bottom (or worse, embedded in the middle somewhere). In this case, there are 7 pages of quotation before your one-sentence contribution. (That is on my laptop. I dread to think how many pages it represents on a smartphone screen...) Usually, if I need to scroll even to the second page-worth of quotation and have still not found any new text, I now just delete the post without reading it. It is a failure to communicate well, on the part of the writer who values their own time more highly than that of their intended readers. Even the much-maligned top-posting style, as forced upon Outlook users (and as I am doing right here), is preferable to the failure to trim, or to get to the point quickly. My inbox has >1600 unread messages in it, and life is just too short. So I offer this plea as a constructive social suggestion - if you want your ideas to reach their intended audience, don't annoy them before they have even seen what you want to say. Regards, Malcolm On 15 Jan 2012, at 20:33, Matthew Farkas-Dyck wrote:
On 13/01/2012, Simon Peyton-Jones
wrote: Thanks to Greg for leading the records debate. I apologise that I don't have enough bandwidth to make more than an occasional contribution. Greg's new wiki page, and the discussion so far has clarified my thinking, and this message tries to express that new clarity. I put a conclusion at the end.
Simon
Overview ~~~~~~~~ It has become clear that there are two elements to pretty much all the proposals we have on the table. Suppose we have two types, 'S' and 'T', both with a field 'f', and you want to select field 'f' from a record 'r'. Somehow you have to disambiguate which 'f' you mean.
(Plan A) Disambiguate using qualified names. To select field f, say (S.f r) or (T.f r) respectively.
(Plan B) Disambiguate using types. This approach usually implies dot-notation. If (r::S), then (r.f) uses the 'f' from 'S', and similarly if (r::T).
Note that
* The Frege-derived records proposal (FDR), uses both (A) and (B) http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
* The Simple Overloaded Record Fields (SORF) proposal uses only (B) http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
* The Type Directed Name Resolution proposal (TDNR) uses only (B)
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio...
I know of no proposal that advocates only (A). It seems that we are agreed that we must make use of types to disambigute common cases.
Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated.
Here's an example:
type family F a b data instance F Int [a] = Mk { f :: Int }
g :: F Int b -> () h :: F a [Bool] -> ()
k x = (g x, x.f, h x)
Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended.
Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families
This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated.
Fortunately we know exactly what to do; it is described in some detail in our paper "Modular type inference with local assumptions" http://www.haskell.org/haskellwiki/Simonpj/Talk:OutsideIn
The trick is to *defer* all these decisions by generating *type constraints* and solving them later. We express it like this:
G, r:t1 |- r.f : t2, (Has t1 "f" t2)
This says that if r is in scope with type t1, then (r.f) has type t2, plus the constraint (Has t1 "f" t2), which we read as saying
Type t1 must have a field "f" of type t2
We gather up all the constraints and solve them. In solving them we may figure out t1 from some *other* constraint (to the left or right, it's immaterial. That allow us to solve *this* constraint.
So it's all quite simple, uniform, and beautiful. It'll fit right into GHC's type-constraint solver.
But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
What about overloading? ~~~~~~~~~~~~~~~~~~~~~~~ A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
Function 'k' works on any record that has a field 'f'. This may be cool, but it wasn't part of our original goal. And indeed neither FDR nor TDNR offer it.
But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
Is (Plan A) worth it? ~~~~~~~~~~~~~~~~
Once you have (Plan B), and SORF in full glory, plus of course the existing ability to name fields T_f, S_f, if you want, I think it is highly questionable whether we need the additional complexities of (Plan A)?
And I do think (Plan A) has lots of additional complexities that we have not begun to explore yet. The data-family thing above is an example, and I can think of some others.
But even if it was simple, we still have to ask: does *any* additional complexity give enough payoff, if you already have SORF? I suspect not.
Extensions to SORF ~~~~~~~~~~~~~~~~~~ Frege lets you add "virtual fields" to a record type, using an extra RExtension mechanism that I do not yet understand. But SORF lets you do so with no additional mechanism. See "Virtual record selectors" on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields The point is that the existing type-class instance mechanisms do just what we want.
Syntax ~~~~~~ The debate on the mailing list has moved sharply towards discussing lexical syntax. I'm not going to engage in that discussion because while it is useful to air opinions, it's very hard to get agreement. But for the record:
* I don't mind having Unicode alternatives, but there must be ASCII syntax too
* I think we must use ordinary dot for field selection.
* I think it's fine to insist on no spaces; we are already doing this for qualified names, as someone pointed out
* I think we should not introduce new syntax unless we are absolutely forced into it. Haskell's record update syntax isn't great, but we have it.
Conclusion ~~~~~~~~~~ I am driven to the conclusion that SORF is the way to go. - Every other proposal on the table requires SORF (either visibly or invisibly) - When you have SORF, you don't really need anything else
The blocking issues are described on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
b) "Record update" (ditto), most especially for records whose fields have polymorphic types
I posted to the wiki a possible solution to (b): http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Alte...
If we fix these we can move forward.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Hi,
One could also argue that a good email client should automatically hide
long quotes. In fact, I guess many people are not even aware of the problem
because their client does this.
Cheers,
Pedro
On Thu, Jan 19, 2012 at 11:14, Malcolm Wallace
Sorry to pick on your post in particular Matthew, but I have been seeing a lot of this on the Haskell lists lately.
I find it completely unreasonable for a reply to a very long post to quote the entire text, only to add a single line at the bottom (or worse, embedded in the middle somewhere). In this case, there are 7 pages of quotation before your one-sentence contribution. (That is on my laptop. I dread to think how many pages it represents on a smartphone screen...) Usually, if I need to scroll even to the second page-worth of quotation and have still not found any new text, I now just delete the post without reading it.
It is a failure to communicate well, on the part of the writer who values their own time more highly than that of their intended readers. Even the much-maligned top-posting style, as forced upon Outlook users (and as I am doing right here), is preferable to the failure to trim, or to get to the point quickly. My inbox has >1600 unread messages in it, and life is just too short. So I offer this plea as a constructive social suggestion - if you want your ideas to reach their intended audience, don't annoy them before they have even seen what you want to say.
Regards, Malcolm
On 15 Jan 2012, at 20:33, Matthew Farkas-Dyck wrote:
On 13/01/2012, Simon Peyton-Jones
wrote: Thanks to Greg for leading the records debate. I apologise that I don't have enough bandwidth to make more than an occasional contribution. Greg's new wiki page, and the discussion so far has clarified my thinking, and this message tries to express that new clarity. I put a conclusion at the end.
Simon
Overview ~~~~~~~~ It has become clear that there are two elements to pretty much all the proposals we have on the table. Suppose we have two types, 'S' and 'T', both with a field 'f', and you want to select field 'f' from a record 'r'. Somehow you have to disambiguate which 'f' you mean.
(Plan A) Disambiguate using qualified names. To select field f, say (S.f r) or (T.f r) respectively.
(Plan B) Disambiguate using types. This approach usually implies dot-notation. If (r::S), then (r.f) uses the 'f' from 'S', and similarly if (r::T).
Note that
* The Frege-derived records proposal (FDR), uses both (A) and (B) http://hackage.haskell.org/trac/ghc/wiki/Records/NameSpacing
* The Simple Overloaded Record Fields (SORF) proposal uses only (B)
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
* The Type Directed Name Resolution proposal (TDNR) uses only (B)
http://hackage.haskell.org/trac/haskell-prime/wiki/TypeDirectedNameResolutio...
I know of no proposal that advocates only (A). It seems that we are
agreed
that we must make use of types to disambigute common cases.
Complexities of (Plan B) ~~~~~~~~~~~~~~~~~~~~~~~~ Proposal (Plan B) sounds innocent enough. But I promise you, it isn't. There has ben some mention of the "left-to-right" bias of Frege type inference engine; indeed the wohle explanation of which programs are accepted and which are rejected, inherently involves an understanding of the type inference algorithm. This is a Very Bad Thing when the type inference algorithm gets complicated, and GHC's is certainly complicated.
Here's an example:
type family F a b data instance F Int [a] = Mk { f :: Int }
g :: F Int b -> () h :: F a [Bool] -> ()
k x = (g x, x.f, h x)
Consider type inference on k. Initially we know nothing about the type of x. * From the application (g x) we learn that x's type has shape (F Int <something>). * From the application (h x) we learn that x's type has shape (F <something else> [Bool]) * Hence x's type must be (F Int [Bool]) * And hence, using the data family we can see which field f is intended.
Notice that a) Neither left to right nor right to left would suffice b) There is significant interaction with type/data families (and I can give you more examples with classes and GADTs) c) In passing we note that it is totally unclear how (Plan A) would deal with data families
This looks like a swamp. In a simple Hindley-Milner typed language you might get away with some informal heuristics, but Haskell is far too complicated.
Fortunately we know exactly what to do; it is described in some detail in our paper "Modular type inference with local assumptions" http://www.haskell.org/haskellwiki/Simonpj/Talk:OutsideIn
The trick is to *defer* all these decisions by generating *type constraints* and solving them later. We express it like this:
G, r:t1 |- r.f : t2, (Has t1 "f" t2)
This says that if r is in scope with type t1, then (r.f) has type t2, plus the constraint (Has t1 "f" t2), which we read as saying
Type t1 must have a field "f" of type t2
We gather up all the constraints and solve them. In solving them we may figure out t1 from some *other* constraint (to the left or right, it's immaterial. That allow us to solve *this* constraint.
So it's all quite simple, uniform, and beautiful. It'll fit right into GHC's type-constraint solver.
But note what has happened: we have simply re-invented SORF. So the conclusion is this:
the only sensible way to implement FDR is using SORF.
What about overloading? ~~~~~~~~~~~~~~~~~~~~~~~ A feature of SORF is that you can write functions like this
k :: Has r "f" Int => r -> Int k r = r.f + 1
Function 'k' works on any record that has a field 'f'. This may be cool, but it wasn't part of our original goal. And indeed neither FDR nor TDNR offer it.
But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
Is (Plan A) worth it? ~~~~~~~~~~~~~~~~
Once you have (Plan B), and SORF in full glory, plus of course the existing ability to name fields T_f, S_f, if you want, I think it is highly questionable whether we need the additional complexities of (Plan A)?
And I do think (Plan A) has lots of additional complexities that we have not begun to explore yet. The data-family thing above is an example, and I can think of some others.
But even if it was simple, we still have to ask: does *any* additional complexity give enough payoff, if you already have SORF? I suspect not.
Extensions to SORF ~~~~~~~~~~~~~~~~~~ Frege lets you add "virtual fields" to a record type, using an extra RExtension mechanism that I do not yet understand. But SORF lets you do so with no additional mechanism. See "Virtual record selectors" on http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields The point is that the existing type-class instance mechanisms do just what we want.
Syntax ~~~~~~ The debate on the mailing list has moved sharply towards discussing lexical syntax. I'm not going to engage in that discussion because while it is useful to air opinions, it's very hard to get agreement. But for the record:
* I don't mind having Unicode alternatives, but there must be ASCII syntax too
* I think we must use ordinary dot for field selection.
* I think it's fine to insist on no spaces; we are already doing this for qualified names, as someone pointed out
* I think we should not introduce new syntax unless we are absolutely forced into it. Haskell's record update syntax isn't great, but we have it.
Conclusion ~~~~~~~~~~ I am driven to the conclusion that SORF is the way to go. - Every other proposal on the table requires SORF (either visibly or invisibly) - When you have SORF, you don't really need anything else
The blocking issues are described on
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
a) "Representation hiding" (look for that heading)
b) "Record update" (ditto), most especially for records whose fields have polymorphic types
I posted to the wiki a possible solution to (b):
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Alte...
If we fix these we can move forward.
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Hi, On 01/19/2012 10:22 AM, Jos Pedro Magalhes wrote:
One could also argue that a good email client should automatically hide long quotes. In fact, I guess many people are not even aware of the problem because their client does this.
But then what is the point of including the text in the first place if it is understood it is only there to be hidden? Besides, personally, I don't want my e-mail client to attempt (and, short of it truly having an understanding of what is being said, inevitably fail) to understand which parts of an e-mail are of interest to me and which parts are not. No, I agree completely with Malcolm: not taking the time to quote ONLY what is of relevance to provide the immediately relevant context for a point one wishes to make is a failure of communication and, indeed, an abuse of other's time. Thanks, Malcolm, well said! /Henrik -- Henrik Nilsson School of Computer Science The University of Nottingham nhn@cs.nott.ac.uk

On 19/01/2012, Malcolm Wallace
I find it completely unreasonable for a reply to a very long post to quote the entire text, only to add a single line at the bottom (or worse, embedded in the middle somewhere). In this case, there are 7 pages of quotation before your one-sentence contribution. (That is on my laptop. I dread to think how many pages it represents on a smartphone screen...) Usually, if I need to scroll even to the second page-worth of quotation and have still not found any new text, I now just delete the post without reading it.
Regards, Malcolm
Sorry. The reason that I have done so is that my primary mail client (GMail web) automatically folds quoted text (marked by ">" at start of line). (I'm not sure whether my secondary client (mutt) can do so.) When I first saw this message, I thought I would be slammed for top-posts (I have been guilty a few times). Anyhow, I shall keep this in mind.

On January 19, 2012 05:14:30 Malcolm Wallace wrote:
I find it completely unreasonable for a reply to a very long post to quote the entire text, only to add a single line at the bottom (or worse, embedded in the middle somewhere).
+1

On Fri, Jan 13, 2012 at 8:52 PM, Simon Peyton-Jones
But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
So I understand that internally a Has constraint is great for resolving the type. What is the use case for having the Has abstraction externally exposed? I think there is a great temptation for this because we would have a functionality we can point to that has some kind of extensible record capability. But I believe the Has abstraction to be a form of weak-typing more so than a form of extensibility. Just because 2 records have a field with the same name, even with the same type in no way signifies they are related. Instead we need a formal contract for such a relation. We already have that in type classes, and they can already be used for this very capability in a more type-safe way. Let me give an example use case that we would be tempted to use this for: database record projections. Given a record representing a row of a database table data Row = Row { smallName :: String, largeVideo :: ByteString } We may have occasions in which we just use a subset of the database/record fields. display :: Row -> String display r = smallName r We can gain efficiency by just selecting the fields we need from the database (smallName). But now I need to re-factor all my Haskell code to have a projection of the original record with just the needed fields. I could try to make a sum type with projections, but then I need to pattern match on them. This seems like a great use case for a generic Has abstraction, but that broadens the types allowed to outside of just Record. Instead what I really need is a more specific Has abstraction. I want to write just: display :: RowSmallName r => r -> String So instead I should use type classes, but not something generic for all possible records, something for all possible Row projections. I do have to name the different Row types, and deal with re-factoring when field usage changes, but at least I have type-safety. i would like to avoid re-factoring and have the compiler to generate this constraint automatically based on what fields are required in the function. display :: RowProjection "smallName" -> String Ideally this information is propogated back to the point where I create the records. Using the Persistent library: records <- selectList [RecordSmallName .== "Bob"] [] Persistent uses Template Haskell to automatically define serializing to and from records, including the full database query projection. We could generate instances for the different available record projections automatically ahead of time. With this kind of system, we would have an amazing feature of automatic database projection for queries. But maybe this kind of system would end up with horrible error messages or have details that are very difficult to work out. My point is that Has abstractions are weak types and that likely we should be searching for something stronger or using type classes. If I am wrong then we should have convincing use cases outlined before we make this a goal of a records implementation, and still I don't see why it needs to be a blocking requirement if we are just trying to solve the basic records issue. Greg Weber

On 18/01/2012, Greg Weber
On Fri, Jan 13, 2012 at 8:52 PM, Simon Peyton-Jones
wrote: But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
So I understand that internally a Has constraint is great for resolving the type. What is the use case for having the Has abstraction externally exposed?
I think there is a great temptation for this because we would have a functionality we can point to that has some kind of extensible record capability.
But I believe the Has abstraction to be a form of weak-typing more so than a form of extensibility. Just because 2 records have a field with the same name, even with the same type in no way signifies they are related. Instead we need a formal contract for such a relation. We already have that in type classes, and they can already be used for this very capability in a more type-safe way.
My point is that Has abstractions are weak types and that likely we should be searching for something stronger or using type classes. If I am wrong then we should have convincing use cases outlined before we make this a goal of a records implementation, and still I don't see why it needs to be a blocking requirement if we are just trying to solve the basic records issue.
Greg Weber
Has *is* a type class. It can be used and abused like any other. Record members with the same key ought to have the same semantics; the programmer must ensure this, not just call them all "x" or the like. Weak types these are not. The selector type is well-defined. The value type is well-defined. The record type is well-defined, but of course we define a type-class to let it be polymorphic.

On Wed, Jan 18, 2012 at 1:09 PM, Matthew Farkas-Dyck
On 18/01/2012, Greg Weber
wrote: On Fri, Jan 13, 2012 at 8:52 PM, Simon Peyton-Jones
wrote: But, the Has constraints MUST exist, in full glory, in the constraint solver. The only question is whether you can *abstract* over them. Imagine having a Num class that you could not abstract over. So you could write
k1 x = x + x :: Float k2 x = x + x :: Integer k3 x = x + x :: Int
using the same '+' every time, which generates a Num constraint. The type signature fixes the type to Float, Integer, Int respectively, and tells you which '+' to use. And that is exactly what ML does!
But Haskell doesn't. The Coolest Thing about Haskell is that you get to *abstract* over those Num constraints, so you can write
k :: Num a => a -> a k x = x + x
and now it works over *any* Num type.
On reflection, it would be absurd not to do ths same thing for Has constraints. If we are forced to have Has constraints internally, it woudl be criminal not to abstract over them. And that is precisely what SORF is.
So I understand that internally a Has constraint is great for resolving the type. What is the use case for having the Has abstraction externally exposed?
I think there is a great temptation for this because we would have a functionality we can point to that has some kind of extensible record capability.
But I believe the Has abstraction to be a form of weak-typing more so than a form of extensibility. Just because 2 records have a field with the same name, even with the same type in no way signifies they are related. Instead we need a formal contract for such a relation. We already have that in type classes, and they can already be used for this very capability in a more type-safe way.
My point is that Has abstractions are weak types and that likely we should be searching for something stronger or using type classes. If I am wrong then we should have convincing use cases outlined before we make this a goal of a records implementation, and still I don't see why it needs to be a blocking requirement if we are just trying to solve the basic records issue.
Greg Weber
Has *is* a type class. It can be used and abused like any other. Record members with the same key ought to have the same semantics; the programmer must ensure this, not just call them all "x" or the like.
Weak types these are not. The selector type is well-defined. The value type is well-defined. The record type is well-defined, but of course we define a type-class to let it be polymorphic.
I think the concern is -- similarly to "duck typing" -- that unrelated modules or libraries might unintentionally choose the same name for their record fields. This doesn't seem so theoretical. That they would also choose the same -type- is less likely, but the possibility is there. (Especially for things like, say, size :: Int, or name :: String, or that sort of thing.) The way type classes solve this for functions and methods, as compared to duck typing, is that you have to explicitly declare an interface (type class) - it's not merely implied by the name and type - and for types you have to explicitly declare that they support that interface (with an instance). Classes and their methods are distinguished not just by their name, but also by the module they were defined in. So the analogous thing for records, I think, if you want to be super-safe about it, would probably be to explicitly declare the existence of a record field/selector/projector (do we have an agreed upon terminology?) -- I'm not sure whether the type would be declared for the selector or in the record... module ModuleA where selector fieldA :: Int selector fieldB :: a ...and in a potentially different module... module ModuleB where import ModuleA data SomeRecord = SomeRecord { fieldA, fieldB :: String } -- again, I have no idea where or how the types should be handled ...and in a third module... foo :: Has r fieldA (Int?) => r -> Int foo r = r.fieldA If there were more than one 'selector fieldA' declaration in scope, you would have to disambiguate them with the module name in the same way as all other things: foo :: Has r ModuleA.fieldA (Int?) => r -> Int foo r = r.(ModuleA.fieldA) I haven't thought this through and am not in any way recommending it (especially not any of the specific details); I just saw a parallel. (I *am*, however, uncomfortable with using straight-up type level strings, without consideration for any particular alternative. If nothing else they should at least be opaque symbols which can be passed around and used in the supported contexts but not manipulated as strings. String-based hackery should be left to Template Haskell, and out of the type system. I can't really express at the moment why in particular I think it would be bad, but it feels like it would be bad.)

| > Has *is* a type class. It can be used and abused like any other. | > Record members with the same key ought to have the same semantics; the | > programmer must ensure this, not just call them all "x" or the like. | > | > Weak types these are not. The selector type is well-defined. The value | > type is well-defined. The record type is well-defined, but of course | > we define a type-class to let it be polymorphic. I want to mention that the issue Greg raises here is tackled under "Representation hiding". The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way. The trouble with instance declarations is that they are *always* exported. No hiding. Under "Representation hiding" I suggest that * If the record selector "foo" is in scope (by any name), then the corresponding Has instance is in scope too and vice versa. That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances. Simon

On 18/01/2012, Simon Peyton-Jones
| > Has *is* a type class. It can be used and abused like any other. | > Record members with the same key ought to have the same semantics; the | > programmer must ensure this, not just call them all "x" or the like. | > | > Weak types these are not. The selector type is well-defined. The value | > type is well-defined. The record type is well-defined, but of course | > we define a type-class to let it be polymorphic.
I want to mention that the issue Greg raises here is tackled under "Representation hiding".
The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way.
The trouble with instance declarations is that they are *always* exported. No hiding.
Yes. This is a fault. I found a document, "Controlling the scope of instances in Haskell", by Gontijo and Camarão, whose goal is to solve this very problem. http://www.dcc.ufmg.br/~camarao/controlling-the-scope-of-instances-in-Haskel... The link seems broken, but if so, and anyone should want a copy, feel free to tell me and I shall send it. The size is 244 KB.
Under "Representation hiding" I suggest that
* If the record selector "foo" is in scope (by any name), then the corresponding Has instance is in scope too and vice versa.
That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances.
Simon

2012/1/18 Simon Peyton-Jones
| > Has *is* a type class. It can be used and abused like any other. | > Record members with the same key ought to have the same semantics; the | > programmer must ensure this, not just call them all "x" or the like. | > | > Weak types these are not. The selector type is well-defined. The value | > type is well-defined. The record type is well-defined, but of course | > we define a type-class to let it be polymorphic.
I want to mention that the issue Greg raises here is tackled under "Representation hiding".
The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way.
The trouble with instance declarations is that they are *always* exported. No hiding.
Under "Representation hiding" I suggest that
* If the record selector "foo" is in scope (by any name), then the corresponding Has instance is in scope too and vice versa.
That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances.
Simon
I think these are separate issues. To use the analogy with functions again, imagine we could do this: module A where data Foo = Foo foo :: Foo -> Int foo Foo = 9 module B where bar :: HasFunction a "foo" (a -> Int) => a -> Int bar a = foo a module Main where import A import B main = print $ bar Foo Would we like it? The problem isn't that you can access unexported functions (though that would also be a problem), the problem is that you're overloading functions based on only their name and type, and the "foo" you find might have a different meaning from the one you expected. With type classes, we have a mechanism to ensure that code at point A and code at point B are using the same assumptions. If you declare an instance and it doesn't match the assumptions set out for the class, it's a programmer error. But you can't realistically say "don't declare a function with this name and type unless you mean the same thing by it that I mean by it". If we want similar safety for record fields, we could use a similar mechanism. (Again, without addressing the question of what we want, because I don't know.)

2012/1/18 Simon Peyton-Jones
| > Has *is* a type class. It can be used and abused like any other. | > Record members with the same key ought to have the same semantics; the | > programmer must ensure this, not just call them all "x" or the like. | > | > Weak types these are not. The selector type is well-defined. The value | > type is well-defined. The record type is well-defined, but of course | > we define a type-class to let it be polymorphic.
I want to mention that the issue Greg raises here is tackled under "Representation hiding".
The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way.
The trouble with instance declarations is that they are *always* exported. No hiding.
Under "Representation hiding" I suggest that
* If the record selector "foo" is in scope (by any name), then the corresponding Has instance is in scope too and vice versa.
That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances.
Simon
I am not just concerned about Has instances from modules not under my control. I am concerned there is a new implicit rule that must be explained to me and that I have no control over: that every record field in the same module with a given label must have the exact same semantics as all others with the same label. Currently we don't have this rule. Currently we have records with the essentialy the same field labels, but with prefixes so they can coexist, and they need not have the same semantics. There is an extra problem with adding this rule: it is common practice to put types that arguably should be spread across multiple modules into one (module often named Types). Some form of this must be done out of necessity in the case of circular references between types that will otherwise not resolve. By default in Yesod, we do this out of necessity for records representing database tables. We call the module Models and users are free to write functions there. Now we have to explain to them that they should not. What use case for abstraction over record fields is too burdensome to use a type class for? Lacking such a compelling use case we are adding implicit complexity that can result in weak-typing for the unwary user with no compelling benefit. The wiki page for overloaded records suggests that users are expected to write code like this for virtual selectors: instance Has Shape "area" Float where get = area This is a leaky abstraction forcing me to quote function names at the type level. I would rather not be writing this code - explaining this code to someone means delving into implementation details that I don't understand. The overloaded records proposal is vying for "extensibility" by supporting abstraction over record fields with the same name. For this dubious use case that type classes already satisfies we lose user-friendly implementation of obviously useful functionality like virtual selectors, and also explainability. There is something to be said for the fact that I can quickly comprehend explanations of module-based record systems. I have read over the Overloaded Records proposal several times now and I have the gist of it, but I am still confused on details and need another couple reads to look at all the details instead of glossing over them. So far I have found some details on record implementations in four FP languages. Every single one implements a module-like namespace for records, one after abandoning the abstraction over fields approach. There are differing good approaches to convenient access - I think that is where it is appropriate for Haskell to attempt to take a different approach.

The starting point a new records implementation was to be pragmatic
and get something done. Simon has identified that Has constraints are
required to implement records.
However, in general, exposing an internal implementation to a user is
an idea that should give great pause. It makes it difficult to switch
implementations in the future, and there is likely a more ideal
interface for the end user - I believe that is the case for Has
constraints.
So I propose we move forward with implementing Has constraints, but do
not expose it to the user (similar to TDNR). We don't add abstraction
over fields or virtual fields or any special capabilities that would
expose the implementation. After we have a working records
implementation that meets our goal of solving name-spacing, we can
re-visit what the best interface is to present to the user for more
advanced records capabilities.
There were complaints about TDNR previously. I think we can address
them by noting that we are limiting the scope to just records and that
it is just a beginning that will not limit us.
With this in mind, what limitations are left? are updates still a
problem, and can we solve it (for now at least) by being more
demanding of type annotations?
Greg Weber
On Fri, Jan 20, 2012 at 3:22 PM, Greg Weber
2012/1/18 Simon Peyton-Jones
: | > Has *is* a type class. It can be used and abused like any other. | > Record members with the same key ought to have the same semantics; the | > programmer must ensure this, not just call them all "x" or the like. | > | > Weak types these are not. The selector type is well-defined. The value | > type is well-defined. The record type is well-defined, but of course | > we define a type-class to let it be polymorphic.
I want to mention that the issue Greg raises here is tackled under "Representation hiding".
The way we currently prevent random clients of a data type from selecting its "foo" field is by hiding the record selector "foo". Similarly for its data constructors. This is Haskell's way of doing data abstraction; it may not be the best way, but it's Haskell's way.
The trouble with instance declarations is that they are *always* exported. No hiding.
Under "Representation hiding" I suggest that
* If the record selector "foo" is in scope (by any name), then the corresponding Has instance is in scope too and vice versa.
That would match up with Haskell's hiding mechanisms precisely, albeit at the cost of having an ad-hoc rule for "Has" instances.
Simon
I am not just concerned about Has instances from modules not under my control. I am concerned there is a new implicit rule that must be explained to me and that I have no control over: that every record field in the same module with a given label must have the exact same semantics as all others with the same label.
Currently we don't have this rule. Currently we have records with the essentialy the same field labels, but with prefixes so they can coexist, and they need not have the same semantics.
There is an extra problem with adding this rule: it is common practice to put types that arguably should be spread across multiple modules into one (module often named Types). Some form of this must be done out of necessity in the case of circular references between types that will otherwise not resolve.
By default in Yesod, we do this out of necessity for records representing database tables. We call the module Models and users are free to write functions there. Now we have to explain to them that they should not.
What use case for abstraction over record fields is too burdensome to use a type class for? Lacking such a compelling use case we are adding implicit complexity that can result in weak-typing for the unwary user with no compelling benefit.
The wiki page for overloaded records suggests that users are expected to write code like this for virtual selectors:
instance Has Shape "area" Float where get = area
This is a leaky abstraction forcing me to quote function names at the type level. I would rather not be writing this code - explaining this code to someone means delving into implementation details that I don't understand.
The overloaded records proposal is vying for "extensibility" by supporting abstraction over record fields with the same name. For this dubious use case that type classes already satisfies we lose user-friendly implementation of obviously useful functionality like virtual selectors, and also explainability.
There is something to be said for the fact that I can quickly comprehend explanations of module-based record systems. I have read over the Overloaded Records proposal several times now and I have the gist of it, but I am still confused on details and need another couple reads to look at all the details instead of glossing over them.
So far I have found some details on record implementations in four FP languages. Every single one implements a module-like namespace for records, one after abandoning the abstraction over fields approach. There are differing good approaches to convenient access - I think that is where it is appropriate for Haskell to attempt to take a different approach.

| The starting point a new records implementation was to be pragmatic | and get something done. Simon has identified that Has constraints are | required to implement records. I think it'd be overstating it to say "required". But Has constraints do seem to be a modest way to make progress that fits with the rest of the language. | So I propose we move forward with implementing Has constraints, but do | not expose it to the user (similar to TDNR). We don't add abstraction | over fields or virtual fields or any special capabilities that would | expose the implementation. Fair enough. I call this the "ML numeric solution" in the sense that ML lets you use "+" for floating point addition and integer addition, but insists that the choice be made statically; if you write f x = x+x it'll ask which "+" you mean, so you can have f :: Int -> Int or f :: Float -> Float but not both. Haskell generalises by allow you to abstract over the constraint f :: Num a => a -> a Choosing the ML way for records constraints has the advantage of addressing the immediate problem while making as few future commitments as possible. | With this in mind, what limitations are left? are updates still a | problem, and can we solve it (for now at least) by being more | demanding of type annotations? Yes I think updates (of polymorphic fields) are still a problem. It seems like the main difficulty in your current story. Thanks for working on this. I'll await the writeup that you and Anthony Clayden are doing. Simon

[snip]
| So I propose we move forward with implementing Has constraints, but do | not expose it to the user (similar to TDNR). We don't add abstraction | over fields or virtual fields or any special capabilities that would | expose the implementation.
Fair enough. I call this the "ML numeric solution" in the sense that ML lets you use "+" for floating point addition and integer addition, but insists that the choice be made statically; if you write f x = x+x it'll ask which "+" you mean, so you can have f :: Int -> Int or f :: Float -> Float but not both. Haskell generalises by allow you to abstract over the constraint f :: Num a => a -> a
Choosing the ML way for records constraints has the advantage of addressing the immediate problem while making as few future commitments as possible.
I don't call the solution the ML solution. I call it the Haskell solution. In Haskell there is no implicit abstraction over names: one must explicitly create a typeclass instance to abstract over the same function name. I think it is important to maintain the Haskell way of opting-in to typeclasses. If we like, we could have special conveniences to make opting-in to a shared field name as convenient as possible (maybe use deriving).
| With this in mind, what limitations are left? are updates still a | problem, and can we solve it (for now at least) by being more | demanding of type annotations?
Yes I think updates (of polymorphic fields) are still a problem. It seems like the main difficulty in your current story.
Ok, looking into it now. One aspect that complicates updates is that the type of a polymorphic field can be changed during an update. What is the use case for changing the type of a record field on update? This seems to make things harder on the implementer and harder on users to reason about the types in their program. I think it would be easier on everyone to just remove this capability or have an explicit syntax for changing the type to make it easier for both implementer (to distinguish) and user (to declare intent). I am not sure what the syntax would be, or if it should be a separate action or mixed in with a normal record update. Greg Weber
Thanks for working on this. I'll await the writeup that you and Anthony Clayden are doing.
Simon

Hi, Greg Wever wrote:
What is the use case for changing the type of a record field on update?
Lots. For example, I often use in the context of abstract syntax parametrised on the type of some kind of "annotation". A transformation may then take an abstract syntax tree with one type of annotation onto an abstract syntax tree with a different type of annotation. And if I opted to use named fields for my abstract syntax tree nodes, as I tend to do, polymorphic record update is exactly what I need.
This seems to make things harder on the implementer and harder on users to reason about the types in their program.
Can't comment on the former, but I don't find the latter compelling. I find it completely natural and in a well-documented program there will be enough explicit type information around to make it pretty clear what is going on.
I think it would be easier on everyone to just remove this capability
I'd say that would be very unfortunate.
or have an explicit syntax for changing the type to make it easier for both implementer (to distinguish) and user (to declare intent).
Also sounds unfortunate, and might lead to very repetitive code? I have not followed this thread close enough to be able to say for sure, but if the upshot is that code like the one I am alluding to above would need an annotation wherever there is a polymorphic record update today, then that would not be very nice. For all its flaws, the present Haskell "record" system is far from as useless as it sometimes is made out to be, and it would be a pity if the price for fixing some of those flaws were to lose something as useful as update of polymorphic record fields. Best, /Henrik -- Henrik Nilsson School of Computer Science The University of Nottingham nhn@cs.nott.ac.uk

On 2/11/12 9:47 AM, Greg Weber wrote:
What is the use case for changing the type of a record field on update?
I use it all the time. One may just as well ask: What is the use case for changing the type of a Maybe on update? Or what is the use case for changing only one of the types for a tuple update? Records are no different from any other polymorphic data type. We don't require special syntax for changing Maybe A to Maybe B nor for changing (A,B) to (A,C), so why should we treat records any differently? One particular use case is for records with phantom types. For example, say I have a large record which contains both "real" data and also some memoization caches for common queries. I don't want to have to recompute the caches every time I make a small change to the data, instead I'd like to just be able to flip a type-level bit that says "now the caches are dirty". This way I can make a bunch of small changes to the main data, and then only once that is completed I will recompute the caches (because all the querying functions require that the type-level bit says "the caches are clean/up-to-date"). There are, of course, non-phantom examples as well. For instance, consider a record type for dealing with both sound and unsound versions of some kind of yucky data. For example, dealing with commandline argument handling. For the first stage of handling we just want to store the raw Strings (or [String]s, Maybe Strings,...); but then we'll want to parse those strings into ADTs and also perform some sanity checks to make sure the whole configuration is sane. One option of doing this is to have our record parametrized by the types of the fields, so we're converting from (Yuck String String ...) into (Yuck Bool (Maybe (Int,Float)) ...). For both of these examples it would be possible to monomorphize things to have DataDirty/DataClean or YuckRaw/YuckSane. For the latter example, that's probably a better choice; but for the former example it is demonstrably inferior. And of course it is easy to construct additional examples where monomorphization is not actually feasible. The reason to do this sort of thing as polymorphic records is so that you can simultaneously have some functions which care about the type parameters, and other functions which do not. Without type-changing updates the only way to achieve this is with some convoluted hack like defining a type class over all the monomorphic records (and duplicating all the neigh-identical record definitions), or using data families which are non-portable. Neither of those hacks says what you mean, and both require much more sophisticated type analysis than just using type-changing update for records. Type-changing update should not be removed, and rendering it into something distinct from type-unchanging record update is only coherent for phantom type uses of type changes and so cannot apply to non-phantom uses. -- Live well, ~wren

Thanks to Anthony for his DORF proposal, and spending time to clearly
explain it on the wiki.
I have looked over the main page:
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie...
I have a big issue with the approach as presented - it assumes that
record fields with the same name should automatically be shared. As I
have stated before, to avoid circular dependencies, one is forced to
declare types in a single module in Haskell that would ideally be
declared in a multiple modules. Continuing the database example, I
will have multiple tables with a 'name' column, but they do not have
the same meaning.
If I have a function:
helloName person = "Hello, " ++ person.name
The compiler could infer that I want to say hello to inanimate objects!
Note that I am not completely against abstraction over fields, I just
don't think it is the best default behavior.
And the section "Modules and qualified names for records" shows that
the proposal doesn't fully solve the name-spacing issue.
On Sat, Feb 11, 2012 at 9:43 PM, wren ng thornton
On 2/11/12 9:47 AM, Greg Weber wrote:
What is the use case for changing the type of a record field on update?
I use it all the time. One may just as well ask: What is the use case for changing the type of a Maybe on update? Or what is the use case for changing only one of the types for a tuple update? Records are no different from any other polymorphic data type. We don't require special syntax for changing Maybe A to Maybe B nor for changing (A,B) to (A,C), so why should we treat records any differently?
One particular use case is for records with phantom types. For example, say I have a large record which contains both "real" data and also some memoization caches for common queries. I don't want to have to recompute the caches every time I make a small change to the data, instead I'd like to just be able to flip a type-level bit that says "now the caches are dirty". This way I can make a bunch of small changes to the main data, and then only once that is completed I will recompute the caches (because all the querying functions require that the type-level bit says "the caches are clean/up-to-date").
There are, of course, non-phantom examples as well. For instance, consider a record type for dealing with both sound and unsound versions of some kind of yucky data. For example, dealing with commandline argument handling. For the first stage of handling we just want to store the raw Strings (or [String]s, Maybe Strings,...); but then we'll want to parse those strings into ADTs and also perform some sanity checks to make sure the whole configuration is sane. One option of doing this is to have our record parametrized by the types of the fields, so we're converting from (Yuck String String ...) into (Yuck Bool (Maybe (Int,Float)) ...).
For both of these examples it would be possible to monomorphize things to have DataDirty/DataClean or YuckRaw/YuckSane. For the latter example, that's probably a better choice; but for the former example it is demonstrably inferior. And of course it is easy to construct additional examples where monomorphization is not actually feasible.
The reason to do this sort of thing as polymorphic records is so that you can simultaneously have some functions which care about the type parameters, and other functions which do not. Without type-changing updates the only way to achieve this is with some convoluted hack like defining a type class over all the monomorphic records (and duplicating all the neigh-identical record definitions), or using data families which are non-portable. Neither of those hacks says what you mean, and both require much more sophisticated type analysis than just using type-changing update for records. Type-changing update should not be removed, and rendering it into something distinct from type-unchanging record update is only coherent for phantom type uses of type changes and so cannot apply to non-phantom uses.
-- Live well, ~wren
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Greg Weber
Thanks to Anthony for his DORF proposal, and spending time to clearly explain it on the wiki.
I have a big issue with the approach as presented - it assumes that record fields with the same name should automatically be shared. As I have stated before, to avoid circular dependencies, one is forced to declare types in a single module in Haskell that would ideally be declared in a multiple modules. ...
Thanks Greg, but I'm struggling to understand what the difficulty is with sharing the same name, or why your dependencies are circular. Would you be able to post some code snippets that illustrate what's going on (or what's failing to go on)? Or perhaps this is an experience from some application where you weren't using Haskell? Could you at least describe what was in each record type?
Continuing the database example, I will have multiple tables with a 'name' column, but they do not have the same meaning.
If I have a function:
helloName person = "Hello, " ++ person.name
The compiler could infer that I want to say hello to inanimate objects!
So the first question is: * do your fields labelled `name` all have the same type? (Perhaps all String?) * what "meaning" does a name have beyond being a String? Your code snippet doesn't give the types, but if I guess that you intend `person` to be of type `Person`. Then you can give a signature: helloName :: Person -> String If person can be 'anything' then the type inferred from the bare function equation would be: helloName :: r{ name :: String } => r -> String So you could say hello to your cat, and your pet rock. You couldn't say hello to a pile of bricks (unless it's been given a name as an art installation in the Tate Gallery ;-)
Note that I am not completely against abstraction over fields, I just don't think it is the best default behavior.
So what is the best default behaviour, and what is the range of other behaviours you want to support?
And the section "Modules and qualified names for records" shows that the proposal doesn't fully solve the name-spacing issue.
I think it shows only that record field labels can run into accidental name clash in exactly the same way as everything else in Haskell (or indeed in any programming language). And that Haskell has a perfectly good way for dealing with that; and that DORF fits in with it. Greg, please give some specific examples! I'm trying to understand, but I'm only guessing from the fragments of info you're giving. AntC

On Thu, Feb 23, 2012 at 4:25 PM, AntC
Greg Weber
writes: Thanks to Anthony for his DORF proposal, and spending time to clearly explain it on the wiki.
I have a big issue with the approach as presented - it assumes that record fields with the same name should automatically be shared. As I have stated before, to avoid circular dependencies, one is forced to declare types in a single module in Haskell that would ideally be declared in a multiple modules. ...
Thanks Greg, but I'm struggling to understand what the difficulty is with sharing the same name, or why your dependencies are circular. Would you be able to post some code snippets that illustrate what's going on (or what's failing to go on)?
Or perhaps this is an experience from some application where you weren't using Haskell? Could you at least describe what was in each record type?
You can get an idea of things in the section 'Problems with using the module namespace mechanism' here: http://hackage.haskell.org/trac/ghc/wiki/Records?version=4 The attachment that Chris Done left to demonstrate his types seems to be overwritten. I will bring back his text as it seems his point does need to be driven home. A lot of Haskell projects have a separate Types module to avoid issues with circular dependencies.
Continuing the database example, I will have multiple tables with a 'name' column, but they do not have the same meaning.
If I have a function:
helloName person = "Hello, " ++ person.name
The compiler could infer that I want to say hello to inanimate objects!
So the first question is: * do your fields labelled `name` all have the same type? (Perhaps all String?) * what "meaning" does a name have beyond being a String?
Your code snippet doesn't give the types, but if I guess that you intend `person` to be of type `Person`. Then you can give a signature: helloName :: Person -> String
If person can be 'anything' then the type inferred from the bare function equation would be: helloName :: r{ name :: String } => r -> String
So you could say hello to your cat, and your pet rock. You couldn't say hello to a pile of bricks (unless it's been given a name as an art installation in the Tate Gallery ;-)
Of course we know that we can always add type annotations to clarify things. The question is whether we want to be opt-out and have to explain people that they can end up with weakly typed code when they don't want to share fields.
Note that I am not completely against abstraction over fields, I just don't think it is the best default behavior.
So what is the best default behaviour, and what is the range of other behaviours you want to support?
I believe the best default is to not share fields, but instead have the programmer indicate at or outside of the record definition that they want to share fields. Basically just use type-classes how they are used now - as opt-in. But I am OK with making an especially easy way to do this with records if the current techniques for defining typeclasses are seen as to verbose.
And the section "Modules and qualified names for records" shows that the proposal doesn't fully solve the name-spacing issue.
I think it shows only that record field labels can run into accidental name clash in exactly the same way as everything else in Haskell (or indeed in any programming language). And that Haskell has a perfectly good way for dealing with that; and that DORF fits in with it.
Greg, please give some specific examples! I'm trying to understand, but I'm only guessing from the fragments of info you're giving.
AntC
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

I looked at the DORF implementers view
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie...
It appears that we still have no solution for record updates that
change the type of a polymorphic field.
I think this means we need to look to ways to solve this update issue
other than just de-sugaring to typeclasses.
On Thu, Feb 23, 2012 at 5:01 PM, Greg Weber
On Thu, Feb 23, 2012 at 4:25 PM, AntC
wrote: Greg Weber
writes: Thanks to Anthony for his DORF proposal, and spending time to clearly explain it on the wiki.
I have a big issue with the approach as presented - it assumes that record fields with the same name should automatically be shared. As I have stated before, to avoid circular dependencies, one is forced to declare types in a single module in Haskell that would ideally be declared in a multiple modules. ...
Thanks Greg, but I'm struggling to understand what the difficulty is with sharing the same name, or why your dependencies are circular. Would you be able to post some code snippets that illustrate what's going on (or what's failing to go on)?
Or perhaps this is an experience from some application where you weren't using Haskell? Could you at least describe what was in each record type?
You can get an idea of things in the section 'Problems with using the module namespace mechanism' here: http://hackage.haskell.org/trac/ghc/wiki/Records?version=4 The attachment that Chris Done left to demonstrate his types seems to be overwritten. I will bring back his text as it seems his point does need to be driven home. A lot of Haskell projects have a separate Types module to avoid issues with circular dependencies.
Continuing the database example, I will have multiple tables with a 'name' column, but they do not have the same meaning.
If I have a function:
helloName person = "Hello, " ++ person.name
The compiler could infer that I want to say hello to inanimate objects!
So the first question is: * do your fields labelled `name` all have the same type? (Perhaps all String?) * what "meaning" does a name have beyond being a String?
Your code snippet doesn't give the types, but if I guess that you intend `person` to be of type `Person`. Then you can give a signature: helloName :: Person -> String
If person can be 'anything' then the type inferred from the bare function equation would be: helloName :: r{ name :: String } => r -> String
So you could say hello to your cat, and your pet rock. You couldn't say hello to a pile of bricks (unless it's been given a name as an art installation in the Tate Gallery ;-)
Of course we know that we can always add type annotations to clarify things. The question is whether we want to be opt-out and have to explain people that they can end up with weakly typed code when they don't want to share fields.
Note that I am not completely against abstraction over fields, I just don't think it is the best default behavior.
So what is the best default behaviour, and what is the range of other behaviours you want to support?
I believe the best default is to not share fields, but instead have the programmer indicate at or outside of the record definition that they want to share fields. Basically just use type-classes how they are used now - as opt-in. But I am OK with making an especially easy way to do this with records if the current techniques for defining typeclasses are seen as to verbose.
And the section "Modules and qualified names for records" shows that the proposal doesn't fully solve the name-spacing issue.
I think it shows only that record field labels can run into accidental name clash in exactly the same way as everything else in Haskell (or indeed in any programming language). And that Haskell has a perfectly good way for dealing with that; and that DORF fits in with it.
Greg, please give some specific examples! I'm trying to understand, but I'm only guessing from the fragments of info you're giving.
AntC
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Actually, I looked at the SORF page again:
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields
There is now an 'Alternative Proposal' section that claims to solve
polymorphic update.
If anyone has comments on this please let us know!
So my proposal would be to somehow use the SORF Alternative Proposal
to allow for name-spaced records. This should be used to generate
internal constraints and not be exposed to the end user and not
automatically abstract over fields. This leaves all of our future
options open while satisfying the narrow issue at hand.
On Fri, Feb 24, 2012 at 9:27 AM, Greg Weber
I looked at the DORF implementers view http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie...
It appears that we still have no solution for record updates that change the type of a polymorphic field. I think this means we need to look to ways to solve this update issue other than just de-sugaring to typeclasses.

This should be used to generate internal constraints and not be exposed to the end user and not automatically abstract over fields.
Every one of your messages about records stresses your dislike for polymorphic projections, and your insistence that the Has class should be hidden from the user. I've read all of your explanations, but I'm still totally unconvinced. All your arguments about the semantics of labels are based on the way you want to use them, not on what they are. They are projection functions! Semantically, the only difference between them is the types. Polymorphism makes perfect sense and is completely natural. There is nothing "untyped" about it. I feel you are pushing a narrow personal agenda here. I think the Has class would be useful to programmers and no harder to understand than other Haskel classes. It should not be hidden. Barney.

On Fri, Feb 24, 2012 at 2:00 PM, Barney Hilken
This should be used to generate internal constraints and not be exposed to the end user and not automatically abstract over fields.
Every one of your messages about records stresses your dislike for polymorphic projections, and your insistence that the Has class should be hidden from the user. I've read all of your explanations, but I'm still totally unconvinced. All your arguments about the semantics of labels are based on the way you want to use them, not on what they are. They are projection functions! Semantically, the only difference between them is the types. Polymorphism makes perfect sense and is completely natural. There is nothing "untyped" about it.
I feel you are pushing a narrow personal agenda here. I think the Has class would be useful to programmers and no harder to understand than other Haskel classes. It should not be hidden.
Barney, these kinds of statements about "personal agenda" are counterproductive. I have put in a lot of work that nobody, including yourself has been willing to do for years to push Haskell's records forward. I would appreciate respectful disagreement - I think I have earned that much. Please just stick to logical arguments. Greg Weber

I'm sorry Greg, I didn't mean to be offensive. I just feel that all your arguments in favour of this restriction are based on one particular application of records, rather than a general notion of what records are. Obviously this application is what motivates you to do all the valuable work you have done, and I appreciate that. But people are going to use records in many different ways, and I don't think that a restriction which makes perfect sense in your application should be allowed to restrict the ways other people want to write programs. Barney.

Sorry for getting offended!
I agree with your sentiment, which is why I thought having to write an
extra word in a Record declaration to get automatic abstraction over
fields was a good solution to the problem.
But now I am in the position of being the lone dissenter on automatic
abstraction over fields. And I am in disagreement with SPJ, and I
would gladly drop this entire process and just ask him to be a
benevolent dictator on the subject.
So I am ok with automatic abstraction over fields. Ideally we should
have a way to disable it, just as I was proposing we have a way to
enable it. But perhaps having this will help force a solution to the
circular references issues that makes automatic field abstraction
problematic in the first place. After all, Haskell did start with
barely any way to do IO.
On Fri, Feb 24, 2012 at 2:26 PM, Barney Hilken
I'm sorry Greg, I didn't mean to be offensive. I just feel that all your arguments in favour of this restriction are based on one particular application of records, rather than a general notion of what records are. Obviously this application is what motivates you to do all the valuable work you have done, and I appreciate that. But people are going to use records in many different ways, and I don't think that a restriction which makes perfect sense in your application should be allowed to restrict the ways other people want to write programs.
Barney.

Hi Barney,
On Fri, Feb 24, 2012 at 2:00 PM, Barney Hilken
Every one of your messages about records stresses your dislike for polymorphic projections, and your insistence that the Has class should be hidden from the user. I've read all of your explanations, but I'm still totally unconvinced. All your arguments about the semantics of labels are based on the way you want to use them, not on what they are. They are projection functions! Semantically, the only difference between them is the types. Polymorphism makes perfect sense and is completely natural. There is nothing "untyped" about it.
I share Greg's concerns about polymorphic projections. For example, given a function sort :: Ord a => ... we don't allow any 'a' that happens to export a operator that's spelled <= to be passed to 'sort'. We have the user explicitly create an instance and thereby defining that their <= is e.g. a strict weak ordering and thus make sense when used with 'sort'. This explicitness is useful, it communicates the contract of the function to the reader and lets us catch mistakes in a way that automatically polymorphic projections don't. Automatically polymorphic projections feels like Go's structural polymorphism, C++'s templates or C's automatic numeric coercions, and I'm worried it'll lead to problems when used at scale. They're not required to solve the problem we're trying to solve, so lets hurry slowly and don't bake them in together with the namespacing problem. At the very least use two different LANGUAGE pragmas so users can have one without the other. Cheers, Johan

I share Greg's concerns about polymorphic projections. For example, given a function
sort :: Ord a => ...
we don't allow any 'a' that happens to export a operator that's spelled <= to be passed to 'sort'. We have the user explicitly create an instance and thereby defining that their <= is e.g. a strict weak ordering and thus make sense when used with 'sort'. This explicitness is useful, it communicates the contract of the function to the reader and lets us catch mistakes in a way that automatically polymorphic projections don't.
But the difference is that <= is (potentially) an arbitrary function, so we need to be careful that the instances make sense. A formally correct system would insist that all instances are (at least) reflexive and transitive. But in the case of records, we know what the instances are: they are projection functions. Every single (automatically generated) instance does exactly the same thing: it projects out one component of a record. This isn't like OO polymorphism, where "messages" are actually arbitrary functions which could do anything, the polymorphism is exactly the same as that of fst and snd.
At the very least use two different LANGUAGE pragmas so users can have one without the other.
This I can agree with. It was the way that Greg mentioned it in every single email which was starting to worry me. Barney.

On Fri, Feb 24, 2012 at 3:07 PM, Barney Hilken
But the difference is that <= is (potentially) an arbitrary function, so we need to be careful that the instances make sense. A formally correct system would insist that all instances are (at least) reflexive and transitive. But in the case of records, we know what the instances are: they are projection functions. Every single (automatically generated) instance does exactly the same thing: it projects out one component of a record. This isn't like OO polymorphism, where "messages" are actually arbitrary functions which could do anything, the polymorphism is exactly the same as that of fst and snd.
I appreciate the difference and it might be enough of a difference (from e.g. OO systems) that the problems seen there won't show up in Haskell under a new record system. Aside: It is possible to have no scalar fields in records of course. data R = C { compare :: (a -> a -> Ordering) } -- Johan

On Fri, Feb 24, 2012 at 4:15 PM, Johan Tibell
Aside: It is possible to have no scalar fields in records of course. data R = C { compare :: (a -> a -> Ordering) }
Has x f has no semantic content besides type x having an f field; Ord has (at least in the programmer's mind, even if the language can't check it) meaning beyond the simple presence of a compare function. /g -- "Would you be so kind as to remove the apricots from the mashed potatoes?"

J. Garrett Morris
Has x f has no semantic content besides type x having an f field; Ord has (at least in the programmer's mind, even if the language can't check it) meaning beyond the simple presence of a compare function.
/g
Note that under both SORF and DORF, there are three arguments to the `Has` class. The third is specifically to spec or constrain the type of the result. A decl: data Rec a = Ord a => Rec{ flda :: a } Expects: flda :: (r{ flda :: a }, Ord a) => r -> a Where the {...} in the constraint is sugar for the Has class. (DORF and SORF differ slightly in how that's implemented.) AntC

On Fri, Feb 24, 2012 at 11:40 PM, Johan Tibell
Hi Barney,
On Fri, Feb 24, 2012 at 2:00 PM, Barney Hilken
wrote: Every one of your messages about records stresses your dislike for polymorphic projections, and your insistence that the Has class should be hidden from the user. I've read all of your explanations, but I'm still totally unconvinced. All your arguments about the semantics of labels are based on the way you want to use them, not on what they are. They are projection functions! Semantically, the only difference between them is the types. Polymorphism makes perfect sense and is completely natural. There is nothing "untyped" about it.
I share Greg's concerns about polymorphic projections. For example, given a function
sort :: Ord a => ...
we don't allow any 'a' that happens to export a operator that's spelled <= to be passed to 'sort'. We have the user explicitly create an instance and thereby defining that their <= is e.g. a strict weak ordering and thus make sense when used with 'sort'. This explicitness is useful, it communicates the contract of the function to the reader and lets us catch mistakes in a way that automatically polymorphic projections don't.
Automatically polymorphic projections feels like Go's structural polymorphism, C++'s templates or C's automatic numeric coercions, and I'm worried it'll lead to problems when used at scale. They're not required to solve the problem we're trying to solve, so lets hurry slowly and don't bake them in together with the namespacing problem. At the very least use two different LANGUAGE pragmas so users can have one without the other.
I agree completely. This is what I like about DORF: the D stands for "Declared", which is referring to the fact that the contracts are explicit. Record fields aren't automatically polymorphic based on their name and type, as with SORF, rather they are scoped and disambiguated in the same way as classes. My only discontentments are that it requires top-level declarations for each record field, which feels excessive, and that the polymorphism is opt-out, in other words if you declare a record with a given field and a field with that name/type is already in scope, it is automatically considered to be an instance of the same field. (Not that this is not the same as SORF, because if more than one field with the same name/type is in scope you can (and have to) use the usual explicit module qualification to disambiguate them, and you can write a new top-level field declaration to make it explicit that you're not re-using the field which was in scope.) I think both of these problems could be solved at the same time if (a) instead of requiring explicit top-level declarations for fields, declaring a record would also automatically declare its fields, as distinct from any other fields which may have been in scope, and (b) there would be some lightweight syntax you could use within record declarations to specify that you do want to re-use the record field in scope instead of declaring a new one. This has the added benefit that record declarations as currently written would continue to have the same meaning as they currently have. (For the record, I don't see any harm in also allowing explicit top-level field declarations, outside of records, it's the requirement for them which seems onerous.) So in DORF, if you want to declare a Contact record with a name, a phone number, and an address, you would write: fieldLabel name :: Text fieldLabel phoneNumber :: PhoneNumber fieldLabel address :: Address data Contact = Contact { name :: Text, phoneNumber :: PhoneNumber, address :: Address } -- it's unclear whether the type annotations would be belong in the field declarations, in the record, or in both then if you also want to keep track of people as employees, you write fieldLabel position :: Position fieldLabel salary :: Word data Employee = Employee { name :: Text, position :: Position, salary :: Word } And the name field would automatically be shared between them, and could be used polymorphically with either record. but then if you later write... data City = City { name :: Text} that would also automatically re-use the name field, but that would clearly be wrong. It could be avoided by explicitly declaring a new name field beforehand. (I suppose this aspect of the complaint might be overblown, because as you can see when want a new field you always write a fieldLabel declaration, and if you don't you're implying that you're intending to use the existing one. But it's still very verbose.) In my variant of the proposal, declaring the Contact record would look like this: data Contact = Contact { name :: Text, position :: Position, salary :: Int } This would automatically declare the name, position, and salary fields with their associated types (equivalently to the fieldLabel declarations from the previous example). Then for Employee you would write: data Employee = Employee { %name, position :: Position, salary :: Int } where the just-invented-on-the-spot-don't-attach-any-importance-to-it %name syntax would indicate that you want to re-use the name field in scope, while position and salary would be newly declared. And when you write data City = City { name :: Text } you would be declaring a new field. Just like DORF, if you wanted to declare the Employee record in a situation where you already had the name fields from both City and Contact in scope, you would write: data Employee = Employee { %Data.Contact.name, ... } using normal module qualification to disambiguate it. One thing you couldn't do (with either proposal, I think) is declare multiple fields in the same module and with the same name, but which *aren't* meant to be shared. This is just like how you can't declare two classes with the same name in the same module, either. (Some kind of independently introduced submodules feature feels like it might be the appropriate remedy here.) Please correct me if I've misunderstood or mischaracterized any aspect of DORF.

Hi, Just checking my understanding here as I have not followed this thread in all its details. Gabor Lehel wrote:
I agree completely. This is what I like about DORF: the D stands for "Declared", which is referring to the fact that the contracts are explicit. Record fields aren't automatically polymorphic based on their name and type, as with SORF, rather they are scoped and disambiguated in the same way as classes.
So, with both DORF and your variant of it, am I correct in understanding that polymorphic fields, be it universally quantified as in data ARecordType a = C1 { ..., fieldX :: a, ..., fieldY :: a -> a, ... } or existentially quantified as in: data AnotherRecordType = forall a . C2 { ..., fieldU :: a, ..., fieldV :: a -> Int, ... } would no longer be possible? Note that the type variable a in both cases scope just over the constructor(s) of the data type in question. So any attempt at declaring the types of the fields outside of this context, be it explicitly with the fieldLabel notation, or implicitly as per your proposal, would not be possible. E.g. fieldLabel fieldY :: a -> a would presumably mean fieldLabel fieldY :: forall a . a -> a resulting in ARecordType becoming second-order polymorphic where the value of fieldY would *have* to be a polymorphic function, which is very different from the original definition. Similarly, the whole point with the existentially quantification is to allow a number of fields to share some specific but arbitrary type, which again means that any attempt to type these fields outside of the context of the datatype to which they belong would result in something different. Note that fieldU and fieldV cannot be used as projection functions due to escaped type variables, but that field selection by pattern matching is perfectly fine. Both constructions above are very useful and, I'd argue that a design that rules them out actually is a rather poor fit for a language like Haskell. To be completely honest, I, at least, would be much happier keeping working around the present limitations of Haskell's named fields by picking my field names carefully, than losing the above. Or am I missing something? E.g. is the idea that sharing of fields only applies to fields of monomorphic type, i.e. whose type can be declared globally? Best, /Henrik -- Henrik Nilsson School of Computer Science The University of Nottingham nhn@cs.nott.ac.uk

Henrik Nilsson
Hi,
Just checking my understanding here as I have not followed this thread in all its details.
So, with both DORF ''', am I correct in understanding that polymorphic fields, be it universally quantified as in
Good question Henrik! It's explicitly answered in the wiki, because it's a tricky area. Briefly: - both of those varieties of poly fields are possible - both can be declared in records - both can be extracted and applied in polymorphic contexts - DORF supports updating the universally quantified, including changing the type of the field and therefore of the record. (Also there's a Quasifunctor proposal for SORF to do that.) - Neither approach supports updating the existential/higher-ranked variety. (There are all the complexities you discuss in detail. They are solved (with quite a bit of complexity behind the scenes), except for updating h-r types.) You can still use explicit data constructurs to pattern match and update h-r fields. Question for you: (you've already given an example of wanting to update a universally quant'd field and change its type) Do you want to update a h-r field? If so, what's the use case? AntC
data ARecordType a = C1 { ..., fieldX :: a, ..., fieldY :: a -> a, ... }
or existentially quantified as in:
data AnotherRecordType = forall a . C2 { ..., fieldU :: a, ..., fieldV :: a -> Int, ... }
would no longer be possible?
Note that the type variable a in both cases scope just over the constructor(s) of the data type in question. So any attempt at declaring the types of the fields outside of this context, be it explicitly with the fieldLabel notation, or implicitly as per your proposal, would not be possible. E.g.
fieldLabel fieldY :: a -> a
would presumably mean
fieldLabel fieldY :: forall a . a -> a
resulting in ARecordType becoming second-order polymorphic where the value of fieldY would *have* to be a polymorphic function, which is very different from the original definition.
Similarly, the whole point with the existentially quantification is to allow a number of fields to share some specific but arbitrary type, which again means that any attempt to type these fields outside of the context of the datatype to which they belong would result in something different.
Note that fieldU and fieldV cannot be used as projection functions due to escaped type variables, but that field selection by pattern matching is perfectly fine.
Both constructions above are very useful and, I'd argue that a design that rules them out actually is a rather poor fit for a language like Haskell.
To be completely honest, I, at least, would be much happier keeping working around the present limitations of Haskell's named fields by picking my field names carefully, than losing the above.
Or am I missing something? E.g. is the idea that sharing of fields only applies to fields of monomorphic type, i.e. whose type can be declared globally?
Best,
/Henrik

2012/2/25 Gábor Lehel
Please correct me if I've misunderstood or mischaracterized any aspect of DORF.
Okay, I did end up misunderstanding and mischaracterizing at least two aspects of DORF. Re-reading the wiki page: http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... it's clear that you would not have to write fieldLabel declarations for every single field of every single record, only for the ones you wish to be shared and usable polymorphically. By default, fields of individual records would be specific to that record (monomorphic in the type of the record), except if there is a fieldLabel declaration for them in scope in which case they would be considered instances of it. (I hope I have it right this time...) So the difference between DORF and my variant would be: DORF: Fields are record-specific (monomorphic in the record type) by default; having a field be polymorphic requires writing a fieldLabel declaration and having it in scope when the record is declared; if a matching fieldLabel is in scope the field is automatically considered shared and polymorphic in the record type. In other words, you have to write the "classes" explicitly, but the "instances" are inferred automatically. Me: Declaring a record always implies fieldLabel declarations for each of its fields (record-specific, monomorphic-in-the-record-type fields are not possible); these are always *new* fieldLabels, which are not considered to be the same as previous ones and cannot be used interchangeably with them; to re-use an existing fieldLabel for a field of your record you must use explicit syntax. In other words, here the "classes" are automatic, but the "instances" are explicit. It wasn't clear to me before that DORF retains record-monomorphic fields, while my variant does away with them. In DORF you can presumably still use a record-monomorphic field selector to help infer the concrete type of the record (whereas with polymorphic fields inference goes in the other direction). Also, while in both variants it is possible to avoid re-using an existing "field class" for your record, in my variant it's not possible to prevent a downstream record from re-using your "field class" (whereas record-monomorphic fields by definition can't have further instances). So in effect in DORF inside of record declarations you can have two types of fields, record-polymorphic and record-monomorphic, along with separate top-level fieldLabel declarations to declare which ones are the polymorphic fields; while in my variant inside of records you can have two types of fields, "classes" and "instances", with explicit syntax to indicate which ones are the instances. Retaining record-monomorphic fields seems like a flexibility-versus-consistency tradeoff: in DORF you have two types of fields with opposite behaviour with respect to type inference, whereas with my variant you only have one. One troubling consequence of DORF -- again, if I'm understanding things correctly -- is that due to implicit field instances a module import can change the meaning of your program: a record field which was considered record-monomorphic for lack of a matching fieldLabel declaration will be considered polymorphic is one is imported. My variant avoids this. The other aspect of DORF which I mischaracterized in my previous email is that fieldLabel declarations don't look like fieldLabel name :: Text rather, they look like fieldLabel name :: r -> Text where r stands for the type of the record. The implications of this are not clear to me. As Henrik's email helped me realize, I'm completely clueless with regards to how type variables are scoped and handled in DORF. I also don't know how my proposed modifications would affect it. So I'll go back to reading the wiki some more and let Anthony field Henrik's questions in the meantime, if he wants to. (One thing that's obvious is that universally quantified polymorphic fields *are* allowed in DORF, because a specific example is listed which uses one. It's completely inconceivable to me that any record system proposal could be adopted which required doing away with them. Complete show-stopper.)

Gábor Lehel
2012/2/25 Gábor Lehel
: Please correct me if I've misunderstood or mischaracterized any aspect of
DORF.
Okay, I did end up misunderstanding and mischaracterizing at least two aspects of DORF.
Re-reading the wiki page:
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie...
Sorry, Gábor, but you've still mis-understood. I tried so hard to explain it clearly!
it's clear that you would not have to write fieldLabel declarations for every single field of every single record,
But yes you do have to declare every fieldLabel that's going to appear in a record decl within a module compiled under DORF. (Even if the field only appears once in one record -- sorry!) You can, though, mix records/fields from modules compiled under H98 monomorphs (providing no clash of names!)
only for the ones you wish to be shared and usable polymorphically. By default, fields of individual records would be specific to that record (monomorphic in the type of the record),
No! the record decl would (try to) generate an instance for every field of every record, then the compile would fail because there was no fieldLabel declared.
So the difference between DORF and my variant would be: ...
(You've misunderstood DORF, so got the next bit wrong.)
It wasn't clear to me before that DORF retains record-monomorphic fields, ...
(It doesn't!)
In DORF you can presumably still use a record-monomorphic field selector to help infer the concrete type of the record
Kind-of: you can declare a fieldLabel with a monomorphic type (if you want it to only appear in a single record), then it helps type inference.
... One troubling consequence of DORF -- again, if I'm understanding things correctly -- is that due to implicit field instances a module import can change the meaning of your program:
No you aren't understanding correctly, so: no, a module import can't change the meaning. (It might mean the program fails to compile, due to name clash.) I've responded to same later posts to clarify this. AntC

After more pondering, I finally think I understand what the DORFistas want. Here is an example: You want to define records which describe people, and include (among other things) a field called "name". There might be several different record types with a name field, depending on whether the record refers to a customer, an employee, a business contact etc., but in each case "name" is the name of the person to which the record refers. You then write various functions which assume this, such as
spam :: Has r "name" String => r -> String spam r = "Dear " ++ r.name ++ "\nHave you heard..."
Now I want to define records which describe products, and I also use a field "name" in the same way, except that it is the brand name of the product. I also define functions such as
offer :: Has r "name" String => r -> String offer r = "Reduced! " ++ r.name ++ " 50% off!"
It doesn't make any sense to apply your functions to my records or vice-versa, but because we both chose the same label, the compiler allows it. Putting the code in separate modules makes no difference, since labels are global. Here is a simple solution, using SORF: The real problem is that the polymorphism of spam and offer is too general. We should each define new classes
class Has r "name" String => HasPersonalName r class Has r "name" String => HasBrandName r
and make each of our record types an instance of this class
instance HasPersonalName EmployeeRecord instance HasPersonalName CustomerRecord instance HasBrandName FoodRecord
then we can define functions with a more specific polymorphism
spam :: HasPersonalName r => r -> String spam r = "Dear " ++ r.name ++ "\nHave you heard..."
offer :: HasBrandName r => r -> String offer r = "Reduced! " ++ r.name ++ " 50% off!"
Now there is no danger of confusing the two uses of "name", because my records are not instances of HasPersonalName, they are instances of HasBrandName. You only use the class Has if you really want things to be polymorphic over all records, otherwise you use the more specific class. This seems to me a much simpler approach than building the mechanism in to the language as DORF does, and it's also more general, because it isn't hard linked to the module system. Does it have any disadvantages? Barney.

On Sat, Feb 25, 2012 at 3:54 PM, Barney Hilken
After more pondering, I finally think I understand what the DORFistas want. Here is an example:
You want to define records which describe people, and include (among other things) a field called "name". There might be several different record types with a name field, depending on whether the record refers to a customer, an employee, a business contact etc., but in each case "name" is the name of the person to which the record refers. You then write various functions which assume this, such as
spam :: Has r "name" String => r -> String spam r = "Dear " ++ r.name ++ "\nHave you heard..."
Now I want to define records which describe products, and I also use a field "name" in the same way, except that it is the brand name of the product. I also define functions such as
offer :: Has r "name" String => r -> String offer r = "Reduced! " ++ r.name ++ " 50% off!"
It doesn't make any sense to apply your functions to my records or vice-versa, but because we both chose the same label, the compiler allows it. Putting the code in separate modules makes no difference, since labels are global.
Exactly!
Here is a simple solution, using SORF:
The real problem is that the polymorphism of spam and offer is too general. We should each define new classes
class Has r "name" String => HasPersonalName r class Has r "name" String => HasBrandName r
and make each of our record types an instance of this class
instance HasPersonalName EmployeeRecord instance HasPersonalName CustomerRecord instance HasBrandName FoodRecord
then we can define functions with a more specific polymorphism
spam :: HasPersonalName r => r -> String spam r = "Dear " ++ r.name ++ "\nHave you heard..."
offer :: HasBrandName r => r -> String offer r = "Reduced! " ++ r.name ++ " 50% off!"
Now there is no danger of confusing the two uses of "name", because my records are not instances of HasPersonalName, they are instances of HasBrandName. You only use the class Has if you really want things to be polymorphic over all records, otherwise you use the more specific class.
This seems to me a much simpler approach than building the mechanism in to the language as DORF does, and it's also more general, because it isn't hard linked to the module system. Does it have any disadvantages?
I can't tell offhand whether it has any drawbacks with respect to expressiveness. It seems to be a good solution to the stated problem, so thank you for proposing it. My objection is that I'm not sure if there is ever a case where "you really want things to be polymorphic over all records". There is nothing (as far as I know) analogous to this kind of implicit name-based polymorphism anywhere in Haskell. It doesn't seem like the Haskell way to have the less safe thing as the one that's default and convenient, and to allow the programmer to layer a more-safe thing on top of it if he or she wants to. It seems more like the Haskell way to have the safer thing be the default and to require extra work if you want to do something less safe*. In this specific case, is there any actual use case for global implicitly-polymorphic-by-name record fields, where that is actually what you want, and where the DORFish way which is analogous to classes-and-instances wouldn't be appropriate? * (Now granted, if pure code versus unsafePerformIO is white versus black, then this is shade-of-gray versus slightly-darker-shade-of-gray, but the principle is the same.)

On 02/25/2012 10:18 AM, Gábor Lehel wrote:
This seems to me a much simpler approach than building the mechanism in to the language as DORF does, and it's also more general, because it isn't hard linked to the module system. Does it have any disadvantages?
I can't tell offhand whether it has any drawbacks with respect to expressiveness. It seems to be a good solution to the stated problem, so thank you for proposing it.
My objection is that I'm not sure if there is ever a case where "you really want things to be polymorphic over all records". There is nothing (as far as I know) analogous to this kind of implicit name-based polymorphism anywhere in Haskell. [...]
True enough. But DORF doesn't, IMHO, really solve this concern. If you choose to use DORF, then your PersonalName and BrandNames will still be overloaded in just the way you don't want. The only way to avoid this is a pretty arbitrary stylistic decision whether to use Haskell98-style field-name-prefixes or use new-style overloading. Even SORF is better than, say, C++ overloading in the sense that adding another overload in SORF cannot cause code not to compile, nor change its behaviour. Convince me otherwise. -Isaac

On Sat, Feb 25, 2012 at 10:09 PM, Isaac Dupree
On 02/25/2012 10:18 AM, Gábor Lehel wrote:
This seems to me a much simpler approach than building the mechanism in to the language as DORF does, and it's also more general, because it isn't hard linked to the module system. Does it have any disadvantages?
I can't tell offhand whether it has any drawbacks with respect to expressiveness. It seems to be a good solution to the stated problem, so thank you for proposing it.
My objection is that I'm not sure if there is ever a case where "you really want things to be polymorphic over all records". There is nothing (as far as I know) analogous to this kind of implicit name-based polymorphism anywhere in Haskell. [...]
True enough. But DORF doesn't, IMHO, really solve this concern. If you choose to use DORF, then your PersonalName and BrandNames will still be overloaded in just the way you don't want. The only way to avoid this is a pretty arbitrary stylistic decision whether to use Haskell98-style field-name-prefixes or use new-style overloading.
Could you elaborate on this? (What's the way I don't want? What do you mean by field-name-prefixes versus new-style overloading?) With DORF I have control over which fields are polymorphic over which records, very much like how I have control over which classes are polymorphic over which types. That's what I want.
Even SORF is better than, say, C++ overloading in the sense that adding another overload in SORF cannot cause code not to compile, nor change its behaviour.
Sure.
Convince me otherwise.
Your position seems to be that unless there is some kind of grave blocking problem with SORF, then we should go with SORF. I don't really understand this. I think we should go with the best solution available. I think DORF is a better solution than SORF, so we should rather go with DORF than SORF. You've just admitted that there is no actual use case for the behaviour of SORF, as opposed to that of DORF. What am I missing?

On 02/25/2012 05:10 PM, Gábor Lehel wrote:
Could you elaborate on this? (What's the way I don't want? What do you mean by field-name-prefixes versus new-style overloading?) With DORF I have control over which fields are polymorphic over which records, very much like how I have control over which classes are polymorphic over which types. That's what I want.
Darn, I misinterpreted DORF. There was too much text and too many options. Tell me if I'm correct: A. Every declaration with record syntax creates Has instances for all fields [1]. B. "Has", "get" and "set" may not be written by users (guessing due to representation-hiding fail). C. You create functions using "fieldLabel name [...]" D. which have the magical effect of, when in scope unqualified, causing data types defined with record syntax to be accessible through that particular fieldLabel function (and no other way). E. (When two fieldLabels of the same name are in scope unqualified, declaring a record containing that name is an error.) F. So adding an import (for some other reason for your code) that happens to include a fieldLabel can make your records accidentally be more visible, rather than be compile-error or no-effect. I feel weird about record fields having an option that depends on whether something's in scope and cannot be controlled syntactically. Maybe we can fix that without making the syntax worse. G. It is possible (but rather ugly) to use dot-notation when there are multiple fieldNames of the same name in scope. [2] Hmm. Maybe this is Haskelly as well as convenient enough. Did I get everything right? What do you think about my concern about F? [1] http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... [2] http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie...

Isaac Dupree
Darn, I misinterpreted DORF. There was too much text and too many options.
Sorry! You might do better to stick to the implementor's page if you want a less 'option-laden' description. Also better to skip the speculative bits until you've got the basics clear.
Tell me if I'm correct: A. Every declaration with record syntax creates Has instances for all fields [1].
Correct
B. "Has", "get" and "set" may not be written by users (guessing due to representation-hiding fail).
Correct: I want the implementors to have design space for the mechanics behind the scenes.
C. You create functions using "fieldLabel name [...]"
Correct-ish: create both functions and a proxy types. It's really the type that drives inference, not the function.
D. which have the magical effect of, when in scope unqualified, ...
Nothing 'magical' going on: they're ordinary functions and types, with ordinary import/export/hiding. And you can use them qualified if you'd rather.
... causing data types defined with record syntax to be accessible through that particular fieldLabel function (and no other way).
The fieldLabel function behaves very similar to the H98-generated function. The difference is with DORF it's overloaded, but H98 is monomorphic. You can still access the fields as per H98 through pattern match (using the data constructor), or positionally. [Yes I know that if we were designing a 'true' polymorphic record system we'd ban positional access.]
E. (When two fieldLabels of the same name are in scope unqualified, declaring a record containing that name is an error.)
Correct. Just like having any other clash of names in scope (for example all the competing declarations of `map`). And just like those, you can use module qualifiers to resolve the clash.
F. So adding an import (for some other reason for your code) that happens to include a fieldLabel can make your records accidentally be more visible, rather than be compile-error or no-effect.
Wrong: You cannot use a fieldLabel `name` declared in module/namespace A to access a record with a field `name` declared in module B. You'll get a 'no instance' compile fail. Same familiar rules as for any instance resolution. This is the crucial difference compared to SORF: which can't control the scope of its String Kind. (Apologies that I added a speculative discussion of whether DORF could use String Kinds. I said that if doing so would open the 'back door' to the abstraction, then I'll stick with types.)
I feel weird about record fields having an option that depends on whether something's in scope and cannot be controlled syntactically. Maybe we can fix that without making the syntax worse.
Absolutely agree. I designed DORF to correct that deficiency in SORF (as I saw it).
G. It is possible (but rather ugly) to use dot-notation when there are multiple fieldNames of the same name in scope. [2]
Yep, agree with the "ugly".
Hmm. Maybe this is Haskelly as well as convenient enough. Did I get everything right? What do you think about my concern about F?
Well done! Nearly everything. I hope I've allayed your concerns re F. AntC
[1]
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... /ImplementorsView
[2]
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... /DotPostfix#UsingDotnotationamongstqualifiednames

Hi,
Wrong: You cannot use a fieldLabel `name` declared in module/namespace A to access a record with a field `name` declared in module B. You'll get a 'no instance' compile fail. Same familiar rules as for any instance resolution.
This is the crucial difference compared to SORF: which can't control the scope of its String Kind. (Apologies that I added a speculative discussion of whether DORF could use String Kinds. I said that if doing so would open the 'back door' to the abstraction, then I'll stick with types.)
Perhaps this situation could occur though? Module A fieldLabel name String Module B import A -- unknowingly picking up the name label data Foo = Foo { name :: String } -- uses the name label by accident So there'd have to be some syntax to make sure you intend to use a label rather than accidentally use it? (Not that this is a big issue, the situation is surely minor compared to sharing unrelated labels all the time) Oliver

Oliver Batchelor
Hi,
Wrong: You cannot use a fieldLabel `name` declared in module/namespace A to access a record with a field `name` declared in module B. You'll get a 'no instance' compile fail. Same familiar rules as for any instance resolution.
This is the crucial difference compared to SORF: which can't control the
scope
of its String Kind. (Apologies that I added a speculative discussion of whether DORF could use String Kinds. I said that if doing so would open the 'back door' to the abstraction, then I'll stick with types.)
Perhaps this situation could occur though?
Module A fieldLabel name String
Module B import A -- unknowingly picking up the name label
data Foo = Foo { name :: String } -- uses the name label by accident
So there'd have to be some syntax to make sure you intend to use a label rather than accidentally use it? (Not that this is a big issue, the situation is surely minor compared to sharing unrelated labels all the time)
Oliver
Thanks Oliver, hmm ... Did module B import A unqualified? Did module B not have its own declaration of fieldLabel `name`? And presumably module B has set the option to use DORF. Then DORF is going to take it that you mean to share the `name`. (And actually I don't see much harm resulting.) Them's the rules. If there's a fieldLabel `name` in Module B, Foo will use that. If furthermore B imports A unqualified, that's a clash of fieldLabels, so compile fail. If module B is compiled with H98 style records, there's a name clash with the H98 field selector function. I think this is no worse (and no better) than business-as-usual 'accidental' usage-without-declaration matching an unknowingly imported binding. (As part of the 'minimal changes' requirement, I'm trying to avoid syntax changes to record decls.) AntC

On 02/28/2012 06:40 AM, AntC wrote:
Oliver Batchelor
writes: Perhaps this situation could occur though?
Module A fieldLabel name String
Module B import A -- unknowingly picking up the name label
data Foo = Foo { name :: String } -- uses the name label by accident
So there'd have to be some syntax to make sure you intend to use a label rather than accidentally use it? (Not that this is a big issue, the situation is surely minor compared to sharing unrelated labels all the time)
Oliver
Thanks Oliver, hmm ...
Oliver's example is exactly what I feel weird about (and what I refered to as a "magical effect"). Sorry for failing to communicate to you successfully. In the meantime, I had an idea (that could work with SORF or DORF) : data Foo = Foo { name :: String } deriving (SharedFields) The effect is: without that "deriving", the declaration behaves just like H98. (For super flexibility, allow to specify which fields are shared, like "deriving(SharedFields(name, etc, etc))" perhaps.) Is it too verbose? Or too terrible that it isn't a real class (well, there's Has...)? -Isaac

Isaac Dupree
In the meantime, I had an idea (that could work with SORF or DORF) :
data Foo = Foo { name :: String } deriving (SharedFields)
The effect is: without that "deriving", the declaration behaves just like H98.
(For super flexibility, allow to specify which fields are shared, like "deriving(SharedFields(name, etc, etc))" perhaps.)
Is it too verbose? Or too terrible that it isn't a real class (well, there's Has...)?
-Isaac
Thanks Isaac, hmm: that proposal would work against what DORF is trying to do. You're right about the `deriving` syntax currently being used for classes. The fact of re-purposing the surface syntax is really no different to introducing different syntax. Apart from that, it might work for SORF -- and fall into exactly what I don't like about SORF, which is that it sabotages the namespace/module control that applies for every other user-defined name in Haskell. What you're not getting is that DORF quite intentionally helps you hide the field names if you don't want your client to break your abstraction. So under your proposal, a malicious client could guess at the fieldnames in your abstraction, then create their own record with those fieldnames as SharedFields, and then be able to update your precious hidden record type. And guessing the fieldnames is dead easy if you've exported the field selector function, to allow read-only access -- or so you thought. Under DORF, despite the client guessing the fieldnames, they can't use them at all if you don't export them -- because they're in a sealed-off namespace, just like regular Haskell. AntC

On 03/01/2012 01:46 AM, AntC wrote:
Isaac Dupree
writes: In the meantime, I had an idea (that could work with SORF or DORF) :
data Foo = Foo { name :: String } deriving (SharedFields)
The effect is: without that "deriving", the declaration behaves just like H98.
(For super flexibility, allow to specify which fields are shared, like "deriving(SharedFields(name, etc, etc))" perhaps.)
Is it too verbose? Or too terrible that it isn't a real class (well, there's Has...)?
-Isaac
Thanks Isaac, hmm: that proposal would work against what DORF is trying to do.
You're right about the `deriving` syntax currently being used for classes. The fact of re-purposing the surface syntax is really no different to introducing different syntax.
[...]
What you're not getting is that DORF quite intentionally helps you hide the field names if you don't want your client to break your abstraction.
So under your proposal, a malicious client could guess at the fieldnames in your abstraction, then create their own record with those fieldnames as SharedFields, and then be able to update your precious hidden record type.
Show me how a malicious client could do that. Under DORF plus my mini-proposal, module Abstraction (AbstractData) where data AbstractData = Something { field1 :: Int, field2 :: Int } {- or it could use shared field names (shared privately) : fieldLabel field1 --however it goes fieldLabel field2 --however it goes data AbstractData = Something { field1 :: Int, field2 :: Int } deriving (SharedFields) -} module Client where import Abstraction --break abstraction how? let's try... module Client1 where import Abstraction data Breaker = Something { field1 :: Int } deriving (SharedFields) -- compile fails because there are no field-labels in scope module Client2 where import Abstraction fieldLabel field1 --however it goes data Breaker = Something { field1 :: Int } deriving (SharedFields) -- succeeds, still cannot access AbstractData with Client2.field1 module Client3 where import Abstraction -- (using standalone deriving, if we permit it for SharedFields at all) deriving instance SharedFields AbstractData -- compile fails because not all constructors of AbstractData are in scope All my mini-proposal does is modify SORF or DORF to make un-annotated records behave exactly like H98. AntC (in an unrelated reply to Ian) :
I prefer DORF's sticking to conventional/well-understood H98 namespacing controls.
[warning: meta-discussion below; I'm unsure if I'm increasing signal/noise ratio] Since this giant thread is a mess of everyone misinterpreting everyone else, I'm not sure yet that DORF's namespacing is well-understood by anyone but you. For example, one of us just badly misinterpreted the other (above; not sure who yet). Would IRC be better? worse? How can the possibly-existent crowd of quiet libraries@ readers who understand SORF/DORF/etc. correctly show (in a falsifiable way) that they understand? any ideas? Do people misinterpret DORF this much because you posted at least 4000 words[1] without creating and making prominent a concise, complete description of its behaviour? (is that right?) I propose that any new record system have a description of less than 250 words that's of a style that might go in the GHC manual and that causes few if any misinterpretations. Is that too ambitious? Okay, it is. So. Differently, I propose that any new record system have a description of less than 500 words that completely specifies its behaviour and that at least half of libraries@ interprets correctly. (It's fine if the description refers to docs for other already-implemented type-system features, e.g. MPTCs and kind stuff.[2] ) Should we be trying for such a goal? (For reference: just SORF's "The Base Design" section is 223 words, and just DORF's "Application Programmer's view" only up to "Option One" is 451 words. (according to LibreOffice.) Neither one is a complete description, but then, my proposed "500 word description" wouldn't mention design tradeoffs. A GHC User's Guide subsection I picked arbitrarily[3] is 402 words.) [1] I counted the main DORF page plus the one you pointed me to, each of which is about 2000: http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... + http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... [2] My sense is that "(customer_id r) uses familiar type instance resolution [...]" is only a precise enough statement if the user declared the exact, unedited type of customer_id; and that having constraints like "r{ customer_id :: Int }" would need explanation in terms of familiar type inference such as classes. e.g... in a way that would explain "r{ SomeModule.customer_id :: Int }" (is that allowed?). I could try to write such a description and you could tell me where I go wrong... [3] "Record field disambiguation" http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/syntax-extns.html#dis... -Isaac

Isaac Dupree
AntC (in an unrelated reply to Ian) :
I prefer DORF's sticking to conventional/well-understood H98 namespacing controls.
...
I'm not sure yet that DORF's namespacing is well-understood by anyone but you.
No of course I'm not saying DORF's namespacing is well-understood. I mean: 1. H98 namespacing controls are conventional and well understood. 2. DORF uses H98 controls and only them. Re 2: Partly you'll just have to take my word for it, but mainly the implementors will have to prove it to themselves if DORF is ever going to see the light of day, so I'd be daft to claim something I didn't have good evidence for. Also there's strong corroboration: there's a prototype implementation attached to the wiki. You can download it and compile it (one module importing the other), and run it and try to break the abstractions, and demonstrate sharing the fields that are sharable. You can inspect the code to see if I've desugarred my syntax correctly, or introduced some trick. (If you see anything 'suspicious', please ask.) In fact, doing all that would be a far better use of your time (and mine) than all that verbiage and word counting. AntC

Isaac Dupree
So under your proposal, a malicious client could guess at the fieldnames in your abstraction, then create their own record with those fieldnames as SharedFields, and then be able to update your precious hidden record type.
Show me how a malicious client could do that. Under DORF plus my mini-proposal,
module Abstraction (AbstractData) where ... fieldLabel field1 --however it goes [code]
Isaac here's a suggestion: please write up your proposal on a wiki. Don't expect that your readers will have bothered to read any of your posts, not even those a few back in the thread. Don't expect they'll have read the DORF proposal, or the SORF, or TDNR, nor even the front page of the Records wiki. Don't expect anybody will believe your claims unless you produce a prototype showing how you translate your code into legal Haskell. Don't expect anybody will believe your prototype unless it has meaningful field names and is illustrating a realistic business application. Once people look at your code or wiki, don't expect they'll get your syntax right: you'll have to explain that from scratch. Don't expect they'll even bother to get this right:
fieldLabel field1 --however it goes
Don't expect they'll understand the difference between a polymorphic record system vs. the narrow namespacing issue - in fact expect them to make all sorts of suggestions for polymorphic record systems. Don't expect they'll try running the prototype code you laboured so hard to get working. Do expect to get a heap of requests for clarifications, which you also put up on to the wiki so that it grows and grows -- even to explain things which you thought were pretty obvious. Do expect to explain the standard Haskell behaviour that you have not changed. It's not enough to say "This follows standard Haskell behaviour." Do expect to find your wiki page growing and growing. Do expect to get a load of posts starting "I haven't followed everything, ..." or "It's a while since I 'tuned out' of the Records thread, ..." and wanting you to explain all the bits they could read for themselves on the wiki. Then expect they'll pick a few words out of your summary and lambast you for it, even though you politely requested they read the wiki to get the full story (and which they clearly did not do). Throughout all this do expect to remain patient, civil and polite. Do not expect to have a social life or get much sleep. Do expect your wife to ask who you're writing to, and why. AntC

Isaac Dupree
In the meantime, I had an idea (that could work with SORF or DORF) :
data Foo = Foo { name :: String } deriving (SharedFields)
The effect is: without that "deriving", the declaration behaves just like H98.
Thanks Isaac, hmm: that proposal would work against what DORF is trying to
do.
What you're not getting is that DORF quite intentionally helps you hide the field names if you don't want your client to break your abstraction.
So under your proposal, a malicious client could guess at the fieldnames in your abstraction, then create their own record with those fieldnames as SharedFields, and then be able to update your precious hidden record type.
Show me how a malicious client could do that. Under DORF plus my mini-proposal,
module Abstraction (AbstractData) where data AbstractData = Something { field1 :: Int, field2 :: Int } ... --break abstraction how? let's try...
module Client1 where import Abstraction data Breaker = Something { field1 :: Int } deriving (SharedFields) -- compile fails because there are no field-labels in scope
Correct that the fieldLabel is not in scope, so that compile will fail; but what price did you pay? Hint: what did you import with `Abstraction`? Answer: you did not import `field1` selector function, nor the mechanism behind it. So in module Client1 you can't access the `field1` content of a record type AbstractData. OK, that's sometimes something you want: to be able to pass around records of a specific type without allowing the client to look inside them at all. But I was talking about the more common requirement for encapsulation. I want to control access to my record type: the client can read (certain) fields, but not update them. Other fields I don't want the client to even know about. (You've achieved the last part with your Client1, for all of the fields.) (FYI: that's how wiki pages turn out so long; specifying exactly all the ins and outs at that sort of subtle detail.) AntC

My objection is that I'm not sure if there is ever a case where "you really want things to be polymorphic over all records".
Well, I don't have a simple, really convincing example, but there are certainly things I want to play with. More importantly, DORF automatically attaches one class to each label, but this is often not what you want. For example, if you have two fields "firstname" and "lastname" the associated classes are less useful: what you really want is
class (Has r "firstname" String, Has r "lastname" String) => HasPersonalName r
so that you can define
fullname :: HasPersonalName r => r -> String fullname r = r.firstname ++ " " ++ r.lastname
You may also want to define subclasses to express more specific conditions. In general, the compiler cannot automatically deduce what is semantically important: you need to define it yourself. The Has class is the base on which you can build.
It doesn't seem like the Haskell way to have the less safe thing as the one that's default and convenient, and to allow the programmer to layer a more-safe thing on top of it if he or she wants to. It seems more like the Haskell way to have the safer thing be the default and to require extra work if you want to do something less safe*.
I think you are using the word "safe" in a slightly misleading way. None of this is mathematically unsafe, because projections are natural (truly polymorphic). The "safety" that is broken here is nothing to do with the semantics of the language, it is to do with the semantics of the system being implemented, and that is something the compiler cannot infer. As my example above shows, it doesn't always correspond one to one with the labels. The Haskel way is to make things as polymorphic as is mathematically safe, even when this goes beyond the programmers original intention. You can then restrict this polymorphism by giving explicit less general types in the same way as in my examples. I think my approach is more Haskel like. Another important Haskel design consideration is to reuse parts of the language where possible, rather than introduce new structures. Type classes were originally introduced to deal with equality and numeric functions, but were reused for many things including monads. My approach achieves the same as DORF (and more), but using existing language features instead of introducing new ones. Barney.

Barney Hilken
My objection is that I'm not sure if there is ever a case where "you really want things to be polymorphic over all records".
Well, I don't have a simple, really convincing example, but there are
More importantly, DORF automatically attaches one class to each label, but
certainly things I want to play with. this is often not what you want. Barney, you seem to be very confused. Added to that you're mixing up DORF with SORF. Agreed both proposals are similar, but there are crucial differences and you've completely befuddled them. In DORF (and SORF) there is only one class -- namely `Has`, with methods `get` and `set`. SORF 'attaches' one Kind for each label. (I'm not sure 'attaches' is the right word -- perhaps 'provides' is better? It's a String Kind same as the label name.) In DORF you must _declare_ a _type_ for the label. (Hence "**Declared** Overloaded Record Fields".) Since it's declared and it's a type, it has usual namespace (module) control. You can declare as many as you want, providing you respect the namespacing.
For example, if you have two fields "firstname" and "lastname" the associated classes are less useful: what you really want is
class (Has r "firstname" String, Has r "lastname" String) =>
HasPersonalName r
That example is a SORF declaration: it uses String Kinds. The DORF equivalent would be: class (Has r Proxy_firstname String, Has r Proxy_lastname String) => HasPersonalName r Note: familiar Haskell proxy types, _not_ new/untried String Kinds. That Proxy stuff is a mouthful, and easy to mistype. I prefer the sugar: class (r{firstname, lastname :: String} ) => ...
so that you can define
fullname :: HasPersonalName r => r -> String fullname r = r.firstname ++ " " ++ r.lastname
You may also want to define subclasses to express more specific conditions. In general, the compiler cannot automatically deduce what is semantically important: you need to define it yourself. The Has class is the base on which you can build.
...
My approach achieves the same as DORF (and more), but using existing language features instead of introducing new ones.
Barney.
What you say there applies to SORF, not DORF. DORF deliberately uses existing class features and familiar type instance resolution. (Because I didn't like the 'experimental' Kinds in SORF, and you couldn't control their namespace.) So what you call "My approach" is almost identical to DORF -- except that you're confusing it with SORF syntax. What you're criticising is SORF, not DORF. AntC

On 2/25/12 10:18 AM, Gábor Lehel wrote:
On Sat, Feb 25, 2012 at 3:54 PM, Barney Hilken
wrote: After more pondering, I finally think I understand what the DORFistas want. Here is an example:
You want to define records which describe people, and include (among other things) a field called "name". There might be several different record types with a name field, depending on whether the record refers to a customer, an employee, a business contact etc., but in each case "name" is the name of the person to which the record refers. You then write various functions which assume this, such as
spam :: Has r "name" String => r -> String spam r = "Dear " ++ r.name ++ "\nHave you heard..."
Now I want to define records which describe products, and I also use a field "name" in the same way, except that it is the brand name of the product. I also define functions such as
offer :: Has r "name" String => r -> String offer r = "Reduced! " ++ r.name ++ " 50% off!"
It doesn't make any sense to apply your functions to my records or vice-versa, but because we both chose the same label, the compiler allows it. Putting the code in separate modules makes no difference, since labels are global.
Exactly!
FWIW, this is the concern I alluded to earlier. Namely that we may want to have two (or more), er, 'classes' of records--- where a field is polymorphic over an individual class, but we don't want those classes to merge simply because they happened to choose the same name and type for the field. I'm not sure it's a good proposal, but it seems like the only way to handle this issue is to (1) introduce a new kind for semantically-oriented field names, and (2) make the Has class use that kind rather than a type-level string. By (1), what I mean is that rather than referring to the field as "name", we would declare PersonalName and BrandName and then use those in lieu of the string. And if we do that, then (2) demands that we must somehow make explicit which one we mean, should we want the `name` field to be polymorphic for some given record declaration. -- Live well, ~wren

I'm not sure it's a good proposal, but it seems like the only way to handle this issue is to (1) introduce a new kind for semantically-oriented field names, and (2) make the Has class use that kind rather than a type-level string.
The second half of my message showed exactly how to handle the problem, using nothing more than existing Haskel features (and SORF for the record fields). The point is that the extra complexity of DORF is completely unnecessary. Barney.

wren ng thornton
FWIW, this is the concern I alluded to earlier. Namely that we may want to have two (or more), er, 'classes' of records--- where a field is polymorphic over an individual class, but we don't want those classes to merge simply because they happened to choose the same name and type for the field.
I agree 'classes' is a misleading word for that. Isn't what you want two (or more) namespaces for records -- that is, for their fields? This is the DORF approach. (Barney seems to be describing the SORF approach -- he's got DORF wrong.) One of the namespaces includes Product.name; another Person.name. (So I'm taking a namespace as equivalent to a module.) You can have multiple records with a `name` field in each module (Customer, Employee, Business Contact, etc) -- so this is already better than H98.) Providing you're coding in a module that imports only one of those namespaces, you can use `name` unqualified. If you're coding in a module that imports both, you must use `name` qualified. If you try to apply (Product.name customer) you'll get a type failure (no instance).
I'm not sure it's a good proposal, but it seems like the only way to handle this issue is to (1) introduce a new kind for semantically-oriented field names,
That's what SORF does: the String Kind
and (2) make the Has class use that kind rather than a type-level string.
No proposal is using a _type_-level string. Barney's confused you. DORF uses a type (regular importable/qualifiable/hidable) with a prefix to the field name: data Proxy_name Where you're in a module that imports both the `name`s per above, the desugarrer would generate Product.Proxy_name and Person.Proxy_name. (That's all rather awkward to get right, which is why I prefer the sugar.) By (1), what I mean is that rather
than referring to the field as "name", we would declare PersonalName and BrandName and then use those in lieu of the string. And if we do that, then (2) demands that we must somehow make explicit which one we mean, should we want the `name` field to be polymorphic for some given record declaration.
AntC

On 2/28/12 3:57 AM, AntC wrote:
wren ng thornton
writes: I'm not sure it's a good proposal, but it seems like the only way to handle this issue is to (1) introduce a new kind for semantically-oriented field names,
That's what SORF does: the String Kind
and (2) make the Has class use that kind rather than a type-level string.
No proposal is using a _type_-level string. Barney's confused you.
I was under the impression that all the working proposals were using the Has class, a la: someFunction :: Has "name" a => a -> Foo someFunction x = ... (name x) ... modulo the debate about the value-level syntax for records, and modulo the debate about whether Has should be exposed to users or hidden inside GHC. Is this no longer the case? -- Live well, ~wren

On 2/29/12 10:51 PM, wren ng thornton wrote:
On 2/28/12 3:57 AM, AntC wrote:
wren ng thornton
writes: I'm not sure it's a good proposal, but it seems like the only way to handle this issue is to (1) introduce a new kind for semantically-oriented field names,
That's what SORF does: the String Kind
and (2) make the Has class use that kind rather than a type-level string.
No proposal is using a _type_-level string. Barney's confused you.
I was under the impression that all the working proposals were using the Has class, a la:
someFunction :: Has "name" a => a -> Foo someFunction x = ... (name x) ...
modulo the debate about the value-level syntax for records, and modulo the debate about whether Has should be exposed to users or hidden inside GHC. Is this no longer the case?
Ah, it seems the exact nature of the Has class is still being debated. I suppose my concern places me in the pro-DORF (or at least anti-SORF) camp. Carry on :) -- Live well, ~wren

wren ng thornton
That's what SORF does: the String Kind
No proposal is using a _type_-level string. Barney's confused you.
I was under the impression that all the working proposals were using the Has class, ...
Yes, but:
a la:
someFunction :: Has "name" a => a -> Foo someFunction x = ... (name x) ...
No! Have you read any of the proposals? Which bit of your anatomy did you use? Note that that syntax is not valid H98. Also note that the `Has` class uses three arguments, and in a different sequence to what you show. That syntax (with 3 args present) turns up in two places: 1. SPJ's SORF proposal 2. Barney Hilken's postings. Please ignore them, is all I can say. Neither I nor SPJ had anything to do with them. The DORF syntax is: someFunction :: Has r Proxy_name t => r -> t I prefer the sugar, which is the same for SORF and DORF (SPJ invented it, I stole it unashamedly): someFunction :: r{ name :: t } => r -> t The difference between the desugarring is crucial: - SORF desugars to a String Kind -- hence Has r "name" t => ... the "name" in quotes represents a String Kind - DORF desugars to a (proxy) type, not some new-fangled Kind Has r Proxy_name t => ... That is H98 (with multi-param type classes). I propose we use the sugar, so that the implementor can decide how to, um, implement.
modulo the debate about the value-level syntax for records, and modulo the debate about whether Has should be exposed to users or hidden inside GHC. Is this no longer the case?
Was _never_ the case, you've been paying too much attention to the wrong postings. Look at the wiki pages: that's why I posted it. I repeat: nobody is using a "type-level string". You (or someone) is making it up. This is beginning to exasperate me. Read the wikis: can you see "type-level string"? Certainly not on the DORF pages, I'm pretty sure not on the SORF pages. If you find it somewhere else, tell me and I'll get it changed. AntC

On Wed, Feb 29, 2012 at 11:05 PM, AntC
I repeat: nobody is using a "type-level string". You (or someone) is making it up.
It isn't clear where that idea came from.
On Thu, Sep 15, 2011 at 7:51 AM, Simon Peyton-Jones
Yes, it would, and of course any impl of TDNR would need an internal constraint similar to your Select. Â In my original proposal I was hiding that, but it has to be there in the implementation. Â But you are right that making it explicit might be a good thing. Â Esp with Julien's new kind stuff (coming soon) we should be able to say
class Select (rec :: *) (fld :: String) where type ResTy rec fld:: * get :: rec -> ResTy rec fld
data T = MkT { x,y :: Int } instance Select T "x" where get (MkT {x = v}) = v
Oh.
On Mon, Jan 2, 2012 at 4:38 AM, Simon Peyton-Jones
It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level). <snip> Specifically
* Allow String as a new kind
* Now you can define classes or types with kinds like
MyCls :: String -> a -> Constraint
T :: String -> *
* Provide type-level string literals, so that “foo” :: String
Huh. You may want to call your type-level-things-that-identify-fields strings, labels, fieldLabels, or rumbledethumps, but surely that's not the point of interest here? /g -- "Would you be so kind as to remove the apricots from the mashed potatoes?"

J. Garrett Morris
On Wed, Feb 29, 2012 at 11:05 PM, AntC
wrote:
I repeat: nobody is using a "type-level string". You (or someone) is making it up.
It isn't clear where that idea came from.
On Mon, Jan 2, 2012 at 4:38 AM, Simon Peyton-Jones
wrote: It seems to me that there's only one essential missing language feature, which is appropriately-kinded type-level strings (and, ideally, the ability to reflect these strings back down to the value level).
* Provide type-level string literals, so that “foo” :: String
Huh.
Thank you Garrett, I feel suitably chided. So the 'culprit' is 'your man himself'.
You may want to call your type-level-things-that-identify-fields strings, labels, fieldLabels, or rumbledethumps, but surely that's not the point of interest here?
/g
Ah, but there _is_ a point of interest: under DORF I _must_ call my type-level- things-etc: **types** (or perhaps proxy **types**), Because they are only and exactly **types**. And because they are exactly **types** they come under usual namespace control. SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them because they're new-fangled, I'm opposed because I can't control the namespace.) AntC

On Wed, Feb 29, 2012 at 11:58 PM, AntC
SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them because they're new-fangled, I'm opposed because I can't control the namespace.)
Nah, they have kinds, and they don't take parameters, so they're probably types. Whether you prefer that "foo" in module A mean the same thing as "foo" in module B is entirely up to you; while it might seem intuitive to do so, it's also true that if I write
data List t = Cons t (List t) | Nil
in two different modules, I declare two entirely distinct list types, even if the "natural" semantics of the two types might be hard to distinguish. /g -- "Would you be so kind as to remove the apricots from the mashed potatoes?"

J. Garrett Morris
On Wed, Feb 29, 2012 at 11:58 PM, AntC
wrote: SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them because they're new-fangled, I'm opposed because I can't control the namespace.)
Nah, they have kinds, and they don't take parameters, so they're probably types. Whether you prefer that "foo" in module A mean the same thing as "foo" in module B is entirely up to you; ... /g
It's about representation hiding: - I don't want the importer to even know I have field "foo", - but they can use my field "bar" Or perhaps: - I don't want the importer to update field "foo" - but they can read "foo", and they can update "bar" (This is especially to support using records to emulate OO, where we want abstraction/'separation of concerns'.) If the importer (either maliciously or by accident) creates their own record with a "foo" field, I specifically _don't_ want them to try sharing my hidden "foo". AntC

On Thu, Mar 01, 2012 at 07:58:42AM +0000, AntC wrote:
SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them because they're new-fangled, I'm opposed because I can't control the namespace.)
I haven't followed everything, so please forgive me if this is a stupid question, but if you implement this variant of SORF: http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop... then do you get the behaviour of SORF when using field names starting with a lower-case letter, and DORF when they start with an upper-case letter? Thanks Ian

On Thu, Mar 1, 2012 at 8:38 AM, Ian Lynagh
On Thu, Mar 01, 2012 at 07:58:42AM +0000, AntC wrote:
SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them because they're new-fangled, I'm opposed because I can't control the namespace.)
I haven't followed everything, so please forgive me if this is a stupid question, but if you implement this variant of SORF:
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop...
then do you get the behaviour of SORF when using field names starting with a lower-case letter, and DORF when they start with an upper-case letter?
Thanks Ian
It is close to a hack (e.g. taking over a special meaning for String) that has been implemented in the Scratchpad II (now known as AXIOM) system for over 3 decades. I found it odd, this maybe for Haskell it may have a completely different taste. If you have a copy of the AXIOM book http://www.amazon.com/AXIOM-Scientific-Computation-Richard-Jenks/dp/03879785... have a look at the end of page 71. -- Gaby

Ian Lynagh
On Thu, Mar 01, 2012 at 07:58:42AM +0000, AntC wrote:
SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them
because
they're new-fangled, I'm opposed because I can't control the namespace.)
I haven't followed everything, so please forgive me if this is a stupid question, but if you implement this variant of SORF:
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop... ntrolbygeneralisingtheStringtypeinHas
then do you get the behaviour of SORF when using field names starting with a lower-case letter, and DORF when they start with an upper-case letter?
Thanks Ian
And you get "In my opinion, this is ugly, since the selector can be either a type name or a label and the semantics are nonsame. Rather, we need scoped instances." [SPJ] So if we open the gate for "ugly", do we also open it for "hacks" and for "unscalable"? Then we also have a solution for updating higher-ranked typed fields. I guess this is all for decision by the implementors. If we need to go into scoped instances, I'd be really scared -- that seems like a huge, far-reaching change, with all sorts of opportunity for mysterious compile fails and inexplicable behaviour-changing from imports. I have some creative ideas for introducing overlapping instances; shall I run them up the flagpole as well? AntC

On Thu, Mar 01, 2012 at 08:52:29PM +0000, AntC wrote:
Ian Lynagh
writes: On Thu, Mar 01, 2012 at 07:58:42AM +0000, AntC wrote:
SORF's whadyoumaycalls are at the Kind level. (I'm not opposed to them
because
they're new-fangled, I'm opposed because I can't control the namespace.)
I haven't followed everything, so please forgive me if this is a stupid question, but if you implement this variant of SORF:
http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields#Scop... ntrolbygeneralisingtheStringtypeinHas
then do you get the behaviour of SORF when using field names starting with a lower-case letter, and DORF when they start with an upper-case letter?
And you get "In my opinion, this is ugly, since the selector can be either a type name or a label and the semantics are nonsame. Rather, we need scoped instances." [SPJ]
That comment was from strake888, not SPJ? Personally, in the context of Haskell (where the case of the first character often determines the behaviour, e.g. a pattern of Foo vs foo), I don't think it's too bad.
So if we open the gate for "ugly", do we also open it for "hacks" and for "unscalable"?
Then we also have a solution for updating higher-ranked typed fields.
I guess this is all for decision by the implementors.
If we need to go into scoped instances, I'd be really scared -- that seems like a huge, far-reaching change, with all sorts of opportunity for mysterious compile fails and inexplicable behaviour-changing from imports.
I have some creative ideas for introducing overlapping instances; shall I run them up the flagpole as well?
I'm getting lost again. But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF. Thanks Ian

Ian Lynagh
On Thu, Mar 01, 2012 at 08:52:29PM +0000, AntC wrote:
And you get "In my opinion, this is ugly, ...
That comment was from strake888, not SPJ?
Thanks Ian, you're right. Specifically, it's 'igloo's tweak to the proposal and 'strake888's comment. (I had erroneously thought the whole of that page was SPJ's, and I hadn't much re-read it since SPJ posted it.)
Personally, in the context of Haskell (where the case of the first character often determines the behaviour, e.g. a pattern of Foo vs foo), I don't think it's too bad.
Hmm. Upper case in an expression always means data constructor (or qualified name). (You've possibly not been watching the outrage at changing the meaning of `.` ;-) Also this would be ambiguous: object.SubObject.Field.subField -- are the `SubObject` and `Field` (private) field selectors, -- or a qualified name for subField? -- or perhaps SubObject.Field is a qualified private field selector? Putting parentheses would cut out some of those interpretations, but not all of them?? In terms of scope control, I think (I'm guessing rather) you do get similar behaviour to DORF, with the added inconvenience of: * an extra arg to Has (how does the constraint sugar cope?) r{ field :: Int } => ... r{ Field :: Int } => ... -- ? does that look odd -- suppose I have two private namespaces r{ Field :: Int ::: Field1 } => ... -- ?? r{ (Field ::: Field2) :: Int } => ... -- ??? * something(?) extra in record decls: data PublicRecord = Pub { field :: Int } data PrivateRecord = Priv { Field :: Int } -- ? data PrivateRecord = Priv { Field :: Int ::: Field2 } -- ?? * a need for equality constraints between Kind and Type (that's the ft ~ FieldT bit) The class decl and instances are 'punning' on tyvar `ft` being both a type and a Kind. Is that even contemplated with Kinds? * a need for something(?) different on record update syntax: pubRec{ field = 27 } privRec{ Field = 92 } -- does upper case there look odd to you? privRec{ Field = 87 ::: Field2 } ("ugly" is a mild reaction, the more I think about it.)
But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF.
No, not the "user to choose", but the implementor. We can't possibly try to support both approaches. AntC

AntC
Ian Lynagh
writes: But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF.
No, not the "user to choose", but the implementor. We can't possibly try to support both approaches.
Sorry, I mis-interpreted your last paragraph. I think you meant: ... allow the user to choose [public or restricted namespacing] behaviour under either the SORF or DORF proposal. Yes-ish (leaving aside that issue). Under SORF you hve an extra behaviour: - use String Kinds and your label is public-everywhere and completely uncontrollable. - (So someone who imports your label can't stop it getting re-exported.) - This is unlike any other user-defined name in Haskell. I'm not sure whether to call that extra behaviour a 'feature' (I tend more to 'wart'), but it's certainly another bit of conceptual overload. I prefer DORF's sticking to conventional/well-understood H98 namespacing controls. AntC

On Thu, Mar 01, 2012 at 11:32:27PM +0000, AntC wrote:
AntC
writes: Ian Lynagh
writes: But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF.
No, not the "user to choose", but the implementor. We can't possibly try to support both approaches.
Sorry, I mis-interpreted your last paragraph. I think you meant:
... allow the user to choose [public or restricted namespacing] behaviour under either the SORF or DORF proposal.
Yes, exactly.
Yes-ish (leaving aside that issue). Under SORF you hve an extra behaviour: - use String Kinds and your label is public-everywhere and completely uncontrollable. - (So someone who imports your label can't stop it getting re-exported.) - This is unlike any other user-defined name in Haskell.
I'm not sure whether to call that extra behaviour a 'feature' (I tend more to 'wart'), but it's certainly another bit of conceptual overload.
Right, but other people would prefer the SORF behaviour to the DORF behaviour. But note that if this was implemented, then the only difference between the 3 is in the desugaring. So if you desugar r.f only then you get SORF, r.F only then you get DORF (well, with different syntax, probably), and if you desugar both then you get the choice. Thanks Ian

Ian Lynagh
On Thu, Mar 01, 2012 at 11:32:27PM +0000, AntC wrote:
AntC
writes: Ian Lynagh
writes: But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF.
No, not the "user to choose", but the implementor. We can't possibly try
to
support both approaches.
Sorry, I mis-interpreted your last paragraph. I think you meant:
... allow the user to choose [public or restricted namespacing] behaviour under either the SORF or DORF proposal.
Yes, exactly.
Yes-ish (leaving aside that issue). Under SORF you hve an extra behaviour: - use String Kinds and your label is public-everywhere and completely uncontrollable. - (So someone who imports your label can't stop it getting re-exported.) - This is unlike any other user-defined name in Haskell.
I'm not sure whether to call that extra behaviour a 'feature' (I tend more to 'wart'), but it's certainly another bit of conceptual overload.
Right, but other people would prefer the SORF behaviour to the DORF behaviour.
Would they? How could we know? Most of the posts here have been from people who don't get anywhere near to understanding the issues. There's been a voicifereous poster who wants to have lots of fields with the same name and have them each "mean" something different. (No, I don't understand either.) Under DORF they could get the public-everywhere behaviour by exporting and importing unqualified (just like H98!).
But note that if this was implemented, then the only difference between the 3 is in the desugaring. So if you desugar r.f only then you get SORF, r.F only then you get DORF (well, with different syntax, probably), and if you desugar both then you get the choice.
Thanks Ian
Sorry Ian, but I've got conceptual overload. I feel I understand DORF behaviour not just because I designed it, but also because I can (and have!) prototyped it under GHC v7.2, including public-everywhere and controlled import/export -- see my additional attachment to the implementor's page. With Kinds and Stringy Kinds and type-to-Kind equality constraints I feel I want to better understand how that affects the design space. I don't think that's possible yet, even in v7.4(?) Right from the beginning of SPJ's SORF proposal, I've had a feeling that ghc central, having introduced the new whizzy Kinds, now wants to find a use for them. Surely there would be other applications for Kinds that would be clearer use cases than patching-up Haskell's kludgy record design? We're focussing too narrowly on this representation-hiding issue. There are other important differences between SORF and DORF (which I've tried to explain on my comparison page on the wiki). Nothing you've said so far is being persuasive. (BTW on the comparison wiki, I've put some speculations around using Kinds behind the scenes as an implementation for DORF -- implementor's choice. Because it's behind the scenes we could use a more compact/specific variety of Kind than String. But it's still in danger of suffering the uncontrollable public-everywhere issue. Could you suggest an improvement?) AntC

On Fri, Mar 2, 2012 at 1:06 AM, Ian Lynagh
On Thu, Mar 01, 2012 at 11:32:27PM +0000, AntC wrote:
Yes-ish (leaving aside that issue). Under SORF you hve an extra behaviour: - use String Kinds and your label is public-everywhere and completely uncontrollable. - (So someone who imports your label can't stop it getting re-exported.) - This is unlike any other user-defined name in Haskell.
I'm not sure whether to call that extra behaviour a 'feature' (I tend more to 'wart'), but it's certainly another bit of conceptual overload.
Right, but other people would prefer the SORF behaviour to the DORF behaviour.
Who and why? What's the use case? I was trying to tease this out at another point in the thread. What use case is there for which Haskell's normal and familiar classes-and-instances mode of polymorphism isn't appropriate, and for which we want to introduce this new and alien global-implicit-name-based mode of polymorphism? Another point which could sway in SORF's favour might be easier implementation, but DORF actually requires less type system magic than SORF, and also already has a working prototype implementation, so I don't think that works, either. Let's look at this from the other direction. The advantage of DORF over SORF is that it handles record fields in a "hygienic" way, and that it works with the module system, rather than around it. What advantage does SORF have over DORF? My main complaint against DORF is that having to write fieldLabel declarations for every field you want to use is onerous. If that could be solved, I don't think there are any others. (But even if it can't be, I still prefer DORF.)

On Fri, Mar 02, 2012 at 01:44:45AM +0100, Gábor Lehel wrote:
On Fri, Mar 2, 2012 at 1:06 AM, Ian Lynagh
wrote: Right, but other people would prefer the SORF behaviour to the DORF behaviour.
Who and why? What's the use case?
My main complaint against DORF is that having to write fieldLabel declarations for every field you want to use is onerous.
I believe this is the main concern people have, but I can't speak for them. Thanks Ian

Gábor Lehel
...
... My main complaint against DORF is that having to write fieldLabel declarations for every field you want to use is onerous. If that could be solved, I don't think there are any others. (But even if it can't be, I still prefer DORF.)
Thank you Gábor, I understand that 'complaint'. I have been trying to keep the design 'clean': either the module is totally DORF, or it's totally H98. But I've also tried to conform to H98 style where possible. So: * DORF field selectors are just functions, like H98 field selector functions. * dot syntax is just reverse apply, so could be used for H98 selectors * pattern syntax still works, and explicit record constructor syntax (relying on DisambiguateRecordFields) * record update syntax is the same (but with a different desugarring) * record decl syntax is the same (but desugars to a Has instance, instead of a function) There have been several suggestions amongst the threads to mix H98-style fields with DORF-style records (or perhaps I mean vice-versa!): * We'd need to change the record decl syntax to 'flag' DORF fields (somehow). * H98 fields desugar to monomorphic field selector functions, as usual. So if you have more than one in scope, that's a name clash. * DORF fields desugar to Has instances. (providing you've declared the fieldLabel somewhere) Perhaps we could take advantage of knowing it's DORF to pick up the field type from the fieldLabel decl? I think you could then 'mix and match' DORF and H98 fields in your expressions and patterns (that was certainly part of my intention in designing DORF). There's one difficulty I can see: * record update would have to know which sort of field it was updating in: r{ fld = expr } If `fld` is DORF, this desugars to a call to `set`. If H98, this code stands as is. What about: r{ fldH98 = expr1, fldDORF = expr2, fldH983 = expr3, fldDORF4 = expr4 } I think: * for DORF updates `set` can only go one field at a time, so it turns into a bunch of nested `set`s (One for fldDORF, inside one for fldDORF4.) * for H98 it can do simultaneous, so in effect we go: let r' = r{ fldDORF = expr2, fldDORF4 = expr4 } -- desugar to nested in r'{ fldH98 = expr1, fldH983 = expr3 } Remaining question: how do we tell a DORF field from a H98, at the point of the record update expression? What is the difference? Find the field selector in the environment from the name: - if monomorphic, it's H98 - if overloaded, it's DORF But! but! we don't know its type until the type inference phase. Yet we need to desugar the syntax at the syntax phase(!) Suggestions please! Also an obfuscation factor: perversely, the record type and field labels might have been exported, but not the selector function. AntC

AntC
Gábor Lehel
writes: ...
... My main complaint against DORF is that having to write fieldLabel declarations for every field you want to use is onerous. If that could be solved, I don't think there are any others. (But even if it can't be, I still prefer DORF.)
Thank you Gábor, I understand that 'complaint'.
I have been trying to keep the design 'clean': either the module is totally DORF, or it's totally H98.
... There have been several suggestions amongst the threads to mix H98-style fields with DORF-style records (or perhaps I mean vice-versa!): * We'd need to change the record decl syntax to 'flag' DORF fields (somehow). ... There's one difficulty I can see: ...
Suggestions please!
Wow! well thank you for all that hard thought going into my question. I've put up a tweak to the proposal as Option Three: "Mixed In-situ and Declared ORF". This does _not_ re-introduce H98 style fields, but does simulate them in a way that fits better with DORF. Do I dub this MIDORF? How will the cat with the hariballs pronounce it ;-)? [Oh, and sorry Isaac: the word count on the wiki has gone up some more.] AntC

Gábor Lehel
..., but DORF actually requires less type system magic than SORF, and also already has a working prototype implementation, ...
... My main complaint against DORF is that having to write fieldLabel declarations for every field you want to use is onerous. ...
(Here's a possible way to reduce the need for declarations, for non-sharing fields; also avoid the fieldLabel new decl.) SPJ introduced some fancy type-handling for higher-ranked fields. But still it only helped in accessing a H-R field, not updating it. If we accept that updating H-R fields in liberated-namespace records is not possible, can we simplify the implementation? I think so. Here's what I'd call a well-principled work-round (not a hack, but then neither a solution). (I'm showing it in DORF-like style, but I think it would work equally in SORF style with Stringy Kinds. The advantage of showing DORF is that we can try it - - which I've done.) You write: data HR = HR{ objectId :: ObjectId -- type Pun , rev :: forall a. [a] -> [a] } -- SPJ's example sharing (ObjectId) deriving (...) -- new syntax -- this decl is not sharing `rev`, so no further code needed -- HR is sharing objectId, so you need a field Label in scope: -- probably you're already declaring newtypes/data newtype ObjectId = ObjectId Int -- declaring a field deriving (Has, ... ) -- `Has` makes it a label Field access can be polymorphic: f :: HR -> ([Bool], [Char]) f r = (r.rev [True], r.rev "hello") Record update looks like: ... myHR{ rev = Rev reverse } ... -- annoying pun But perhaps we could support sugar for it: ... myHR{ Rev reverse } ... -- fewer keystrokes! The HR decl desugars to: newtype Rev = Rev (forall a. [a] -> [a]) -- newtype punning data HR = HR{ objectId :: ObjectId, rev :: Rev } rev :: HR -> (forall a. [a] -> [a]) -- monotype field selector rev r = let (Rev fn) = get r (undefined :: Rev) in fn instance Has HR Rev where get HR{ rev } _ = rev -- have to wrap the fn set (Rev fn) HR{ .. } = HR{ rev = (Rev fn) } type instance FieldTy HR Rev = Rev -- have to wrap this -- else update don't work So I've simplified `Has` to two type arguments (like the more naieve form SPJ considers then rejects). And used the field's type itself as the index (so that we're punning on the field name): class Has r fld where get :: r -> fld -> FieldTy r fld set :: fld -> r -> SetTy r fld -- for type change update For the record type's `sharing` fields we desugar to: instance Has HR ObjectId where get HR{ objectId } _ = objectId -- yeah dull, dull set x HR{ .. } = HR{ objectId = x, .. } and the `deriving (Has)` on the newtype or data desugars to: objectId :: {Has r ObjectId} => r -> ObjectId -- overloaded record objectId r = get r (undefined :: ObjectId) -- selector type instance FieldTy r ObjectId = ObjectId -- not parametric AntC

On Thu, Mar 01, 2012 at 10:46:27PM +0000, AntC wrote:
Also this would be ambiguous:
object.SubObject.Field.subField
Well, we'd have to either define what it means, or use something other than '.'.
In terms of scope control, I think (I'm guessing rather) you do get similar behaviour to DORF, with the added inconvenience of: * an extra arg to Has (how does the constraint sugar cope?)
You can infer ft from the f.
r{ field :: Int } => ... r{ Field :: Int } => ... -- ? does that look odd
Well, it's new syntax.
-- suppose I have two private namespaces r{ Field :: Int ::: Field1 } => ... -- ?? r{ (Field ::: Field2) :: Int } => ... -- ???
You've lost me again.
But I think you are agreeing that (leaving aside the issue of whether the design is reasonable) the above variant would indeed allow the user to choose the behaviour of either SORF or DORF.
No, not the "user to choose", but the implementor. We can't possibly try to support both approaches.
I don't follow. You agreed above that "you do get similar behaviour to DORF", and if you just use lowercase field names then the behaviour is the same as SORF. Therefore both are supported. Thanks Ian

Ian Lynagh
* an extra arg to Has (how does the constraint sugar cope?)
You can infer ft from the f.
Let me explain better what I mean by "two private namespaces", then we'll try to understand how your proposal goes ... module T where data FieldT = Field data RecT = RecT{ Field :: Int } ... module U where data FieldU = Field data RecU = RecU{ Field :: Bool } ... module V where import T -- also consider either/both import U -- imports hiding (Field) data RecV = RecV{ Field :: String } -- am I sharing this Field? -- who with? ... ... r.Field ... -- is this valid?, if not what is? ... r{ Field = e } -- likewise (Oh yeah, imports and hiding: how do I do that for these non-String-type-Kinds? And is this allowed?: data NotaField = Constr Int Bool data AmIaRec = AmI{ Constr :: String } ... ... r.Constr ... It's all getting very tangled trying to squeeze constructors into other roles.) AntC

On Fri, Mar 02, 2012 at 01:04:13AM +0000, AntC wrote:
Let me explain better what I mean by "two private namespaces", then we'll try to understand how your proposal goes ...
module T where data FieldT = Field data RecT = RecT{ Field :: Int } ... module U where data FieldU = Field data RecU = RecU{ Field :: Bool } ... module V where import T -- also consider either/both import U -- imports hiding (Field) data RecV = RecV{ Field :: String } -- am I sharing this Field? -- who with?
Ah, I see. No, you couldn't do that, just as you couldn't do v = Field You would need to say data RecV = RecV{ T.Field :: String }
... r.Field ... -- is this valid?, if not what is?
r!T.Field (I took the liberty of using a random different symbol for field access, for clarity).
... r{ Field = e } -- likewise
r{ T.Field = e }
(Oh yeah, imports and hiding: how do I do that for these non-String-type-Kinds? And is this allowed?: data NotaField = Constr Int Bool data AmIaRec = AmI{ Constr :: String }
No. Thanks Ian

On Fri, Mar 02, 2012 at 01:04:13AM +0000, AntC wrote:
Let me explain better what I mean by "two private namespaces", then we'll try to understand how your proposal goes ...
Folks, it has been very difficult keeping up with all the twists and turns of this thread. So here's a summary of my proposal, by popular demand, http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... #ThumbnailSketch Reminder about this approach: * It requires no new semantics from Haskell (beyond ghc v7.2 extensions). * Therefore a prototype can be developed, and it has! you can download and see for yourself. * The syntax for record updates is the same as it is today. (Although the semantics is polymorphic, not monomorphic.) * The syntax for data declarations is the same as it is today. * The syntax for field selection is the same as it is today. (But polymorphic for any record with a field of that name.) * There is a dot notation syntax possible for field selection, (strictly optional, sugar only). * It does support type-changing update to records, for parametric polymorphic fields and records. But: * This approach does not have a solution for updating fields of higher-rank types. (In that respect it is like SORF. This is the remaining problem. And I suspect that any solution for SORF will equally work for DORF.) There were some criticisms about how much boilerplate this approach would need. There are some ideas/suggestions to improve that. In particular see "Option Three: Mixed In-situ and Declared ORF" further down the page from the Thumbnail Sketch. Constructive feedback welcome. AntC

Barney Hilken
After more pondering, I finally think I understand what the DORFistas want.
Barney, 1) please don't personalise. I designed DORF, built a proof of concept, got it running, asked for feedback (got some very useful thoughts from SPJ), built a prototype, posted the wiki pages. I'm not a "DORFista"; I'm somebody who wants to improve the Haskell records namespace issue. 2) whether or not you think you know what some people want, you don't understand DORF, and you've mixed it up with SORF. You've then caused a great long thread of confusion. In particular, at exactly where DORF is designed to avoid (what I see as) a weakness in SORF, you've alleged DORF has that weakness.
Here is an example: ... (by the way, you've used SORF syntax in those examples)
It doesn't make any sense to apply your functions to my records or vice- versa,
Exactly! and that's what the DORF design avoids, whereas SORF suffers from it.
but because we both chose the same label,
SORF uses the "same label" in the sense of the same String Kind.
the compiler allows it. Putting the code in separate modules makes no difference, since labels are global.
DORF's labels are not global, they're proxy _types_ so that the scope is controlled in the usual way. So using separate modules makes all the difference.
Here is a simple solution, using SORF: ...
I think your "solution" would work just as well 'translated' into DORF. (But then it's a "solution" to something that isn't a problem in DORF.)
... than building the mechanism in to the language as DORF does, ...
Barney.
DORF is not "building the mechanism in to the language", nor is it introducing any new language features, only sugar. The prototype runs in GHC v7.2.1. All I've done is hand-desugarred. (Look at it for yourself, it's attached to the implementor's page.) SORF, on the other hand, needs user-defined Kinds, which are only just being introduced in v7.4, and don't yet include String Kind. AntC

On 2/24/12 5:40 PM, Johan Tibell wrote:
I share Greg's concerns about polymorphic projections. For example, given a function
sort :: Ord a => ...
we don't allow any 'a' that happens to export a operator that's spelled<= to be passed to 'sort'. We have the user explicitly create an instance and thereby defining that their<= is e.g. a strict weak ordering and thus make sense when used with 'sort'. This explicitness is useful, it communicates the contract of the function to the reader and lets us catch mistakes in a way that automatically polymorphic projections don't.
Automatically polymorphic projections feels like Go's structural polymorphism, C++'s templates or C's automatic numeric coercions, and I'm worried it'll lead to problems when used at scale. They're not required to solve the problem we're trying to solve, so lets hurry slowly and don't bake them in together with the namespacing problem. At the very least use two different LANGUAGE pragmas so users can have one without the other.
+1. I'm not sure that I like the current proposals for how to control the non/automatic-ness of polymorphism (for reasons I can spell out later, if desired). But we definitely want to have something that's a bit more cultured than simply making all record projectors polymorphic over records. -- Live well, ~wren

wren ng thornton
I'm not sure that I like the current proposals for how to control the non/automatic-ness of polymorphism (for reasons I can spell out later, if desired). But we definitely want to have something that's a bit more cultured than simply making all record projectors polymorphic over records.
Wren, I'm not sure if you've got it straight. (It's a subtle issue.) This is an area where SORF differs from DORF: - SORF can't hide the representation of a given field name (so a client program can 'guess' a field "identifier") That's because SORF is driven by String Kind, which cannot be scope controlled. - DORF uses (Proxy) types for (roughly) the same purpose as the String Kinds. But because they're types, you can control the scope, and keep the abstraction. AntC

Barney Hilken
This should be used to generate internal constraints and not be exposed to the end user and not automatically abstract over fields.
Barney, I'm not going to try to defend or explain what Greg means by that comment, but I do want to talk about:
insistence that the Has class should be hidden from the user. ... I think the Has class would be useful to programmers and no harder to understand than other Haskel classes. It should not be hidden.
Barney.
I agree that what the `Has` class is doing is useful, and indeed the programmer needs to understand it. SPJ suggested syntactic sugar for the Has class. The DORF proposal embraces it whole-heartedly, and expects the programmer would use the sugar. There's two reasons for avoiding writing explicit `Has`: 1. It's tricky syntax and easy to get wrong. Specifically, all your examples in later posts have confused it. 2. The implementation of overloadable record fields (whether SORF or DORF) is going to be tricky to get right. We'll need equality and class constraints. There needs to be some space for the implementors to tune the design. I think the sugar is hiding the implementation, not the existence of the `Has` class -- as is good practice. AntC

Greg Weber
I looked at the DORF implementers view
http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... /ImplementorsView
It appears that we still have no solution for record updates that change the type of a polymorphic field.
There's two varieties of polymorphic fields: - the type-changing variety DORF has a solution for update/changing the type of field and record (always has since the proof of concept in December) SORF has a speculative suggestion of Quasifunctor (looks like this needs tightening up??) - the higher-ranked variety neither proposal has a way of updating (well, DORF did but it's an unscalable "hack" [SPJ], I put it on the wiki anyway, in case somebody can improve it) AntC

On 18/01/2012, Gábor Lehel
(I *am*, however, uncomfortable with using straight-up type level strings, without consideration for any particular alternative. If nothing else they should at least be opaque symbols which can be passed around and used in the supported contexts but not manipulated as strings. String-based hackery should be left to Template Haskell, and out of the type system. I can't really express at the moment why in particular I think it would be bad, but it feels like it would be bad.)
I strongly agree; plus, it's awkward (and ugly) that a selector be desugarred to a type-level string of its key identifier, as if it were some perverse quasiliteral. The trouble is, if they were opaque, then how could a function be polymorphic over all records with a certain member, with defined semantics? How could one tell the compiler that the semantics of all such members are the same? One must define a name, and then the problem of namespace non-interoperability that is now a great bother would be a greater bother yet.

On Thu, Dec 29, 2011 at 12:00 PM, Simon Peyton-Jones
| The lack of response, I believe, is just a lack of anyone who | can cut through all the noise and come up with some | practical way to move forward in one of the many possible | directions.
You're right.
There are a few tens of thousands of Haskell programmers now, right? I think a significant fraction of them would in fact appreciate the basic dot syntax and namespace-fixes that TDNR proposed. I fear that record-packages-as-libraries are unlikely to be used by a large number of people. Are they now? I love do-it-in-the-language as a principle, but I've watched it really impair the Scheme community with respect to many language features. (Recall your experiences, if you've had them, with homebrew Scheme OOP systems.) It seems hard for non-standard language extensions to gain wide use. Though, to be fair, Haskell's basic types have a history of being replaced by widely accepted alternatives (Vector, ByteString). In spite of its limitations, was there that much of a negative response to Simon's more recent proposal? http://hackage.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields "+1"! This is a great bang-for-the-buck proposal; it leverages the existing multiparameter type classes in a sensible way. I admit I'm a big fan of polymorphic extension. But I don't love it enough for it to impede progress! Regarding extension: In trying to read through all this material I don't see a lot of love for "lacks" constraints a la TRex. As one anecdote, I've been very pleased using Daan Leijen's scoped labels approach. I implemented it for my embedded stream processing DSL (WaveScript) and wrote
10K lines of application code with it. I never once ran into a bug resulting from shadowed/duplicate fields!
Cheers, -Ryan
But it is very telling that the vast majority of responses on
http://www.reddit.com/r/haskell/comments/nph9l/records_stalled_again_leaders... were not about the subject (leadership) but rather on suggesting yet more, incompletely-specified solutions to the original problem. My modest attempt to build a consensus by articulating the simplest solution I could think of, manifestly failed.
The trouble is that I just don't have the bandwidth (or, if I'm honest, the motivation) to drive this through to a conclusion. And if no one else does either, perhaps it isn't *that* important to anyone. That said, it clearly is *somewhat* important to a lot of people, so doing nothing isn't very satisfactory either.
Usually I feel I know how to move forward, but here I don't.
Simon
_______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Ryan Newton
I admit I'm a big fan of polymorphic extension. But I don't love it enough
for it to impede progress!
Regarding extension: In trying to read through all this material I don't
see a lot of love for "lacks" constraints a la TRex.
Cheers, -Ryan
Hi Ryan, I think the "lacks" constraint is sadly misunderstood, and in fact something like it will be needed eventually. (If anybody who knows the internals of Hugs/TRex is listening, it would be great to get confirmation of the following.) As you say, it relates to whatever might happen for polymorphic records, so is outside the scope of what SPJ is trying to address in this thread. On the other hand, let's try to avoid developing an approach for 'Records in Haskell' that has to be undone when/if we get more polymorphic.
From all the suggestions in the current thread, I'm expecting the approach will include a Has class with methods get and set. It would be sweet if in future the same Has class could be extended to extended(!) records, anonymous records, renamed labels, projections on records, merged records (as you'd get from a relational join), etc. Specifically: Has r l t => ... really must mean there's exactly one field labelled l in record r, at type t (which is a constraint on whatever merged/extended term r is) compare TRex's (r\l) => ... Rec {| l : t | r |} ... which really means there's exactly one field labelled l in the Rec, at type t
As one anecdote, I've been very pleased using Daan Leijen's scoped labels approach My anecdote: the new approaches and extensions to type inference in GHCi have been frustratingly slow in arriving and maturing. But we now have working superclass constraints, including type equality (~), and some heavy-weight type inference. I've built a toy (and somewhat limited) polymorphic record system (with Has/get/set), which: treats Data types as records; and treats tuples as anonymous (type-indexed) records; and implements project, extend and merge. It relies on overlapping instances (so I don't mention it in polite company -- at least it doesn't use FunDeps to boot!). I achieve the effect of 'HasUnique'
In hindsight, I think it was unfortunate that the original TRex paper [1] used the word "lacks" in context of its notation for constraining the record structure. (I'm looking especially at section 2.1 'Basic Operations'.) In all the operations, the types always include a Rec term with _both_ l and r. They don't all have a term of a 'bare': Rec {| r |} TRex is trying to avoid a positional specification of the record fields (which is 'implementation determined'/hidden from the programmer). But I think of 'bare' r as representing a record with a 'hole' at whatever position l is in the full record. (So the constraint (r\l) means: you're going to find a Rec with exactly one l in it; if you also find a Rec with 'bare' r, that means the same Rec, but with a 'hole'.) The HList guys picked up the word "lacks", and adapted it (successfully in context of what they were doing) to build 'Type Indexed Hlist's -- that is, record-like structures with exactly one field labelled l. Perhaps TRex should have used a notation something like: (rr :> l @ t) => Rec {| rr |} ... -- HasUnique rr l t ... Rec {| rr \ l |} ... -- rr with a hole in place of l You say: through instance resolution: if there's more than one occurence of label l in the record term, GHC bitches. (This is fragile: I can't use IncoherentInstances, and sometimes UndecidableInstances gives trouble.) [1] A Polymorphic Type System for Extensible Records and Variants, Gaster/Mark P. Jones, 1996.

Ryan Newton
I admit I'm a big fan of polymorphic extension. But I don't love it enough
for it to impede progress!
Regarding extension: In trying to read through all this material I don't
see a lot of love for "lacks" constraints a la TRex.
Cheers, -Ryan
Hi Ryan, I think the "lacks" constraint is sadly misunderstood, and in fact something like it will be needed eventually. (If anybody who knows the internals of Hugs/TRex is listening, it would be great to get confirmation of the following.) As you say, it relates to whatever might happen for polymorphic records, so is outside the scope of what SPJ is trying to address in this thread. On the other hand, let's try to avoid developing an approach for 'Records in Haskell' that has to be undone when/if we get more polymorphic. [Weird! somewhere between gmaniac and pipermail, half my post went missing: I was only just warming up this far. Try again ...]
From all the suggestions in the current thread, I'm expecting the approach will include a Has class with methods get and set. It would be sweet if in future the same Has class could be extended to extended(!) records, anonymous records, renamed labels, projections on records, merged records (as you'd get from a relational join), etc. Specifically: Has r l t => ... really must mean there's exactly one field labelled l in record r, at type t (which is a constraint on whatever merged/extended term r is) compare TRex's (r\l) => ... Rec {| l : t | r |} ... which really means there's exactly one field labelled l in the Rec, at type t
As one anecdote, I've been very pleased using Daan Leijen's scoped labels approach My anecdote: the new approaches and extensions to type inference in GHCi have been frustratingly slow in arriving and maturing. But we now have working superclass constraints, including type equality (~), and some heavy-weight type inference. I've built a toy (and somewhat limited) polymorphic record system (with Has/get/set), which: treats Data types as records; and treats tuples as anonymous (type-indexed) records; and implements project, extend and merge. It relies on overlapping instances (so I don't mention it in polite company -- at least it doesn't use FunDeps to boot!). I achieve the effect of 'HasUnique'
In hindsight, I think it was unfortunate that the original TRex paper [1] used the word "lacks" in context of its notation for constraining the record structure. (I'm looking especially at section 2.1 'Basic Operations'.) In all the operations, the types always include a Rec term with _both_ l and r. They don't all have a term of a 'bare': Rec {| r |} TRex is trying to avoid a positional specification of the record fields (which is 'implementation determined'/hidden from the programmer). But I think of 'bare' r as representing a record with a 'hole' at whatever position l is in the full record. (So the constraint (r\l) means: you're going to find a Rec with exactly one l in it; if you also find a Rec with 'bare' r, that means the same Rec, but with a 'hole'.) The HList guys picked up the word "lacks", and adapted it (successfully in context of what they were doing) to build 'Type Indexed Hlist's -- that is, record-like structures with exactly one field labelled l. Perhaps TRex should have used a notation something like: (rr :> l @ t) => Rec {| rr |} ... -- HasUnique rr l t ... Rec {| rr \ l |} ... -- rr with a hole in place of l You say: through instance resolution: if there's more than one occurence of label l in the record term, GHC bitches. (This is fragile: I can't use IncoherentInstances, and sometimes UndecidableInstances gives trouble.) [1] A Polymorphic Type System for Extensible Records and Variants, Gaster/Mark P. Jones, 1996.

Ryan Newton
I admit I'm a big fan of polymorphic extension. But I don't love it enough for it to impede progress!
Regarding extension: In trying to read through all this material I don't see a lot of love for "lacks" constraints a la TRex. Cheers, -Ryan
Hi Ryan, I think the "lacks" constraint is sadly misunderstood, and in fact something like it will be needed eventually. [Weird! somewhere between gmaniac and pipermail, half my post went missing: I was only just warming up this far. Try again ... again ...] (If anybody who knows the internals of Hugs/TRex is listening, it would be great to get confirmation of the following.) As you say, it relates to whatever might happen for polymorphic records, so is outside the scope of what SPJ is trying to address in this thread. On the other hand, let's try to avoid developing an approach for 'Records in Haskell' that has to be undone when/if we get more polymorphic.
From all the suggestions in the current thread, I'm expecting the approach will include a Has class with methods get and set. It would be sweet if in future the same Has class could be extended to extended(!) records, anonymous records, renamed labels, projections on records, merged records (as you'd get from a relational join), etc. Specifically: Has r l t => ... really must mean there's exactly one field labelled l in record r, at type t (which is a constraint on whatever merged/extended term r is) compare TRex's (r\l) => ... Rec {| l : t | r |} ... which really means there's exactly one field labelled l in the Rec, at type t
As one anecdote, I've been very pleased using Daan Leijen's scoped labels approach My anecdote: the new approaches and extensions to type inference in GHCi have been frustratingly slow in arriving and maturing. But we now have working superclass constraints, including type equality (~), and some heavy-weight type inference. I've built a toy (and somewhat limited) polymorphic record system (with Has/get/set), which: treats Data types as records; and treats tuples as anonymous (type-indexed) records; and implements project, extend and merge. It relies on overlapping instances (so I don't mention it in polite company -- at least it doesn't use FunDeps to boot!). I achieve the effect of 'HasUnique'
In hindsight, I think it was unfortunate that the original TRex paper [1] used the word "lacks" in context of its notation for constraining the record structure. (I'm looking especially at section 2.1 'Basic Operations'.) In all the operations, the types always include a Rec term with _both_ l and r. They don't all have a term of a 'bare': Rec {| r |} TRex is trying to avoid a positional specification of the record fields (which is 'implementation determined'/hidden from the programmer). But I think of 'bare' r as representing a record with a 'hole' at whatever position l is in the full record. (So the constraint (r\l) means: you're going to find a Rec with exactly one l in it; if you also find a Rec with 'bare' r, that means the same Rec, but with a 'hole'.) The HList guys picked up the word "lacks", and adapted it (successfully in context of what they were doing) to build 'Type Indexed Hlist's -- that is, record-like structures with exactly one field labelled l. Perhaps TRex should have used a notation something like: (rr :> l @ t) => Rec {| rr |} ... -- HasUnique rr l t ... Rec {| rr \ l |} ... -- rr with a hole in place of l You say: through instance resolution: if there's more than one occurence of label l in the record term, GHC bitches. (This is fragile: I can't use IncoherentInstances, and sometimes UndecidableInstances gives trouble.) [1] A Polymorphic Type System for Extensible Records and Variants, Gaster/Mark P. Jones, 1996.

Ryan Newton
I admit I'm a big fan of polymorphic extension. But I don't love it enough
for it to impede progress!
Regarding extension: In trying to read through all this material I don't
see a lot of love for "lacks" constraints a la TRex.
Cheers, -Ryan
As one anecdote, I've been very pleased using Daan Leijen's scoped labels approach. My anecdote: the new approaches and extensions to type inference in GHCi have been frustratingly slow in arriving and maturing. But we now have working superclass constraints, including type equality (~), and some heavy-weight type inference. I've built a toy (and somewhat limited) polymorphic record system (with Has/get/set), which: treats Data types as records; and treats tuples as anonymous (type-indexed) records; and implements project, extend and merge. It relies on overlapping instances (so I don't mention it in polite company -- at least it doesn't use FunDeps to boot!). I achieve the effect of 'HasUnique'
Hi Ryan, I think the "lacks" constraint is sadly misunderstood, and in fact something like it will be needed eventually. [A further thousand apologies for the multi-multiposts! somewhere in pipermail half my posts are going missing: I was only just warming up this far. Try again ... again ... third time ... last time] (If anybody who knows the internals of Hugs/TRex is listening, it would be great to get confirmation of the following.) As you say, it relates to whatever might happen for polymorphic records, so is outside the scope of what SPJ is trying to address in this thread. On the other hand, let's try to avoid developing an approach for 'Records in Haskell' that has to be undone when/if we get more polymorphic. All the suggestions in the current thread lead to an approach with a Has class with methods get and set. It would be sweet if in future the same Has class could be extended to extended(!) records, anonymous records, renamed labels, projections on records, merged records (as you'd get from a relational join), etc. Specifically: Has r l t => ... really must mean there's exactly one field labelled l in record r, at type t (which is a constraint on whatever merged/extended term r is) compare TRex's (r\l) => ... Rec {| l : t | r |} ... which really means there's exactly one field labelled l in the Rec, at type t In hindsight, I think it was unfortunate that the original TRex paper [1] used the word "lacks" in context of its notation for constraining the record structure. (I'm looking especially at section 2.1 'Basic Operations'.) In all the operations, the types always include a Rec term with _both_ l and r. They don't all have a term of a 'bare': Rec {| r |} TRex is trying to avoid a positional specification of the record fields (which is 'implementation determined'/hidden from the programmer). But I think of 'bare' r as representing a record with a 'hole' at whatever position l is in the full record. (So the constraint (r\l) means: you're going to find a Rec with exactly one l in it; if you also find a Rec with 'bare' r, that means the same Rec, but with a 'hole'.) The HList guys picked up the word "lacks", and adapted it (successfully in context of what they were doing) to build 'Type Indexed Hlist's -- that is, record-like structures with exactly one field labelled l. Perhaps TRex should have used a notation something like: (rr :> l @ t) => Rec {| rr |} ... -- HasUnique rr l t ... Rec {| rr \ l |} ... -- rr with a hole in place of l You say: through instance resolution: if there's more than one occurence of label l in the record term, GHC bitches. (This is fragile: I can't use IncoherentInstances, and sometimes UndecidableInstances gives trouble.) [1] A Polymorphic Type System for Extensible Records and Variants, Gaster/Mark P. Jones, 1996.

* Simon Peyton-Jones:
Provoked the (very constructive) Yesod blog post on "Limitations of Haskell", and the follow up discussion, I've started a wiki page to collect whatever ideas we have about the name spacing issue for record fields.
I think there are is an additional approach: nested modules (multiple modules in a single source file) combined with local imports applying to a specific lexical scope only. I think this is how Ocaml tackles this issue. It is more explicit than the approaches presented so far.
participants (41)
-
Andriy Polischuk
-
AntC
-
Barney Hilken
-
Brandon Allbery
-
Brent Yorgey
-
Chris Smith
-
Christopher Done
-
Claus Reinke
-
David Waern
-
Donn Cave
-
Edward Kmett
-
Evan Laforge
-
Florian Weimer
-
Gabriel Dos Reis
-
Ganesh Sittampalam
-
Gershom Bazerman
-
Greg Weber
-
Gábor Lehel
-
Henrik Nilsson
-
Ian Lynagh
-
Iavor Diatchki
-
Isaac Dupree
-
J. Garrett Morris
-
Jan-Willem Maessen
-
Johan Tibell
-
John Lask
-
José Pedro Magalhães
-
Malcolm Wallace
-
Matthew Farkas-Dyck
-
Nicolas Frisby
-
Nils Anders Danielsson
-
Oliver Batchelor
-
Ryan Newton
-
Sebastian Fischer
-
Simon Marlow
-
Simon Peyton-Jones
-
Thomas Schilling
-
Tyson Whitehead
-
Wolfgang Jeltsch
-
wren ng thornton
-
Yitzchak Gale