Re: [Haskell-cafe] Records in Haskell

Thanks Evan, I've had a quick read through.
Thanks for reading and commenting!
It's a bit difficult to compare to the other proposals.
I can't see discussion of extracting higher-ranked functions and applying them in polymorphic contexts. (This is SPJ's `rev` example.)
Putting h-r fields into records is the standard way of emulating object- oriented style. SPJ's view is that requirement is "very important in practice".
(No proposal has a good answer to updating h-r's, which you do discuss.)
Yeah, I've never wanted that kind of thing. I've written in object-oriented languages so it's not just that I'm not used to the feature so I don't feel its lack. And if I did want it, I would probably not mind falling back to the traditional record syntax, though I can see how people might find that unsatisfying. But my suggestion is meant to solve only the problem of composed record updates and redundant "thing"s in 'Thing.thing_field thing'. Not supporting higher-ranked function record fields *only* means that you can't use this particular convenience to compose updates to a higher-ranked field. If you happen to have that particular intersection of requirements then you'll have to fall back to typing more "thing"s for that particular update. My motivation is to solve an awkward thing about writing in haskell as it is, not add a new programming style.
Re the cons 1. "Still can't have two records with the same field name in the same module since it relies on modules for namespacing."
Did you see the DORF precursor page ? http://hackage.haskell.org/trac/ghc/wiki/Records/DeclaredOverloadedRecordFie... /NoMonoRecordFields
I tried to figure out if that would help, but I suspect not. (Looking at the desugar for `deriving (Lens)`, you need the H98 field selector functions.) Then for me, cons 1. is a show-stopper. (I know you think the opposite.)
Yeah, I don't think the DORF precursor stuff is related, because it's all based on typeclasses. I think there are two places where people get annoyed about name clashes. One is where they really want to have two records with the same field name defined in one module. The other is where they are using unqualified imports to shorten names and get a clash from records in different modules. Only the former is a problem, the latter should work just fine with my proposal because ghc lets you import clashing names as long as you don't call them unqualified, and SDNR qualifies them for you. So about the former... I've never had this problem, though the point about circular imports forcing lots of things into the same module is well taken, I have experienced that. In that case: nested modules. It's an orthogonal feature that can be implemented and enabled separately, and can be useful in other ways too, and can be implemented separately. If we are to retain modules as *the* way to organize namespaces and visibility then we should think about fancying-up modules when a namespacing problem comes up. Otherwise you're talking about putting more than one function into one symbol, and that's typeclasses, and now you have to think of something clever to counteract typeclasses' desire to be global (e.g. type proxies). Maybe that's forcing typeclasses too far beyond their power/weight compromise design?
I also don't see whether you can 'hide' or make abstract the representation of a record type, but still allow read-access to (some of) its fields.
If you want a read-only field, then don't export the lens for 'a', export a normal function for it. However, it would mean you'd use it as a normal function, and couldn't pass it to 'get' because it's not a lens, and couldn't be composed together with lenses. I'd think it would be possible to put 'get' and 'set' into different typeclasses and give ReadLenses only the ReadLens dictionary. But effectively we'd need subtyping, so a Lens could be "casted" automatically to a ReadLens. I'm sure it's possible to encode with clever rank2 and existentials and whatnot, but at that point I'm inclined to say it's too complicated and not worth it. Use plain functions. Since 'get' turns a lens into a plain function, you can still compose with '#roField . get (#rwField1 . #rwField2)'. We could easily support 'get (#roField1 . #roField2)' by doing the ReadLens thing and putting (->) into ReadLens, it's just combining rw fields and ro fields into the same composition that would require type gymnastics.
Suppose a malicious client declares a record with field #a. Can you stop them reading and/or updating your field #a whilst still letting them see field #b of your record type?
I don't think it's worth designing to support malicious clients, but if you don't want to allow access to a function or lens or any value, then don't export it. #a can't resolve to M.a if M doesn't export 'a'.
With SDNR, is it possibly to define a polymorphic field selector function? I suspect no looking at the desugar for `deriving (Lens)`, but perhaps I've mis- understood. I mean: get_a r = ?? #a r -- gets the #a field from any record r
Nope, 'r' has to be monomorphic. That's the "no structural polymorphism" thing I was talking about. I don't mind not having it. If we do that I believe we're moving to a fundamentally different type of polymorphism, and, I think, a less principled one than typeclasses.
This mechanism then supports the idea of 'virtual' fields -- SPJ's example of fullName, built from polymorphic firstName and lastName.
The read-only version is a plain function: fullName r = firstName r ++ " " ++ lastName r Because we are using plain functions we don't need to get all fancy with "virtual" things, they're just normal functions. If you want to update fullName as well, then you could write a lens version: fullName r = lens (firstName r ++ " " ++ lastName r) (\name -> let [first, last] = unwords name in set #firstName first . set #lastName last) Also, fclabels has a notion of views where you can express one type in terms of another (say extract two fields as a pair) and then if you update the first element of the pair it updates the record that came from. It seemed complicated, but maybe it has uses I haven't thought of. I suppose if you wanted to have a record with some internal and some external ones, you could then define the external view as another record, and then they can pass around the whole thing but only get to fiddle with the external fields. I would try to solve the problem without views, e.g. nest the externally visible record inside the internal one instead of trying to put all the fields into one record.
[By the way, did you mean to post to the cafe only? Most of the discussion is going on on ghc-users.]
Oops, no I hadn't noticed which list the threads were on, thanks for pointing it out. Ccing ghc-users. Anyway, one thing I think is different about this proposal is what I said before: it aims to solve an awkward thing about haskell as it is, not add a new programming style. So it adds no power whatsoever. It's more limited than the existing record notation, so it's not even a replacement (but from what I understand, the other proposals aren't either). But it takes what is, for me, the 90% use case and eliminates the redundant typing. Maybe it's not nearly 90% for other people, I can only speak for myself.
participants (1)
-
Evan Laforge