-
Notifications
You must be signed in to change notification settings - Fork 273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RecordDotSyntax language extension proposal #282
Conversation
The link is broken in the rendered proposal, where it says "This proposal is discussed at this pull request" |
I strongly support the direction of travel of this proposal. I've wanted to use dot-notation for record selection since forever; and this looks like a very plausible way to do so. The fact that it's been used extensively in a production context helps reassure me that there aren't unexpected consequences. I'd like more clarity about white space.
Perhaps the right way to think about it is that GHC already supports postfix operators: here is the manual section. It would be good to check that the proposal is compatible with treating |
So fast Simon! I was just fixing it 😉 |
Yes, that's how it behaves. |
Yes.
Exactly.
Yes. |
Thanks. Perhaps in due course update the proposal to make these points clear. |
proposals/0000-record-dot-syntax.md
Outdated
|
||
Below are some possible variations on this plan, but we advocate the choices made above: | ||
|
||
* Should `RecordDotSyntax` imply `NoFieldSelectors`? They are often likely to be used in conjunction, but they aren't inseparable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd argue that it should not. Nothing about this proposal requires that selectors not exist, it is merely helpful to avoid clashes (for which DuplicateRecordFields
would work pretty much equivalently).
If a consensus emerges that RecordDotSyntax
+ NoFieldSelectors
+ ... is the right way forward, we could easily add a new extension Haskell2030Records
that implies the conjunction.
In addition to the lack of type-changing updates, the data T = MkT { foo :: forall a . a -> a } then Perhaps this use is rare enough that users can re-enable selector function generation (or define their own selectors) in this case. |
{-# LANGUAGE RecordDotSyntax #-}
import qualified Foo
data Foo = Foo
instance HasField "name" Foo () where hasField = ...
something = ... Foo.name is that
EDIT: Foo.name -- name from module Name
Foo .name -- getField ... Proposal should point this out. |
It may be rare, but it turned out to be a blocker for various ways of fixing #216. |
The loss of polymorphic update is a huge problem, imo. |
This parses as a qualified variable, not field selection. |
The
I would propose that we introduce a
It would also nicely parallel the C++ syntax |
The foo @Int .field1 @Bar .field2 I can imagine having an So for me, the |
In #282 (comment) the
The proposal should somehow specify how juxtaposition works now, it looks like some will be more binding than another: someFun record .field .field2 value should probably be parsed the same as someFun record.field.field2 value i.e. (someFun ((record.field).field2)) value Should or shouldn't be there warnings for omitted front space? It feels like "don't use tabs" thing. |
I shall take care of it. |
|
That's definitely should be pointed out in the proposal. It's an opposite of what I thought it is. |
Maybe best to go to the unresolved questions section. Hard to call the "right" parse here. |
In my opinion, this proposal leaves at least one huge thing to be desired. It proves an "alternate route" to compositional projection (besides generating projection functions directly) by having the Here's one way to do it. Just as
[of course, pick your exact syntax poison of choice -- One nice thing here is you can even parse the "assignment" as an infix operator if desired, so This could also desugar to using I.e., it has |
It'd be great to update the proposal to cover all the syntactic questions here, so that it stands by itself without reading the discussion thread. For example
Make sure the proposal says all this! |
Understood Simon. On it... Done. |
This proposal tries to solve two things at once (which could probably be pointed out more clearly in the proposal):
|
@gbaz, one value of desugaring to a something using class, is that one can write manual instances to the class. I.e. you can do "Classy Lenses" stuff. Compare with writing
It's silly to be so granular here, but OTOH there are various things happening: new syntax, and overloading that syntax with existing @cocreature was first, and I agree
|
In the language of https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0229-whitespace-bang-patterns.rst, In the bit about update syntax of
Why? Have you used polymorphic update? I agree that it's nice and compositional to have polymorphic update, but is it actually useful in practice? For update sections (along the lines of #282 (comment)), I humbly submit |
I'm a bit worried about polymorphic fields. I agree it's not a frequent use case, but at least I'd very much like to have an escape hatch. To that end, I'd like to clarify: in the worst case scenario, a manual accessor written via pattern matching would still work, right? E.g.
|
Co-Authored-By: Arnaud Spiwack <[email protected]>
What are current thoughts on polymorphic updates? Having classes like: class GetField (s :: Symbol) a where
type Field s a
getField :: a -> Field s a
class SetField (s :: Symbol) x a where
type Updated s x a
type Updated s x a = a
setField :: a -> x -> Updated s x a
class (GetField s a, SetField s x a) => HasField s x a
instance (GetField s a, SetField s x a) => HasField s x a It would easy to implement/derive them, use-cases with monomorphic fields would only need to mention concrete field type in instance head and we could have read-only "virtual" fields constructed from values of other fields without having to think about setting in any way. |
Is it possible to use newline to chain multiple selector application, let r = val
.lenghtyFieldName1
.lengthyFieldName2 Would the above be equivalent to the following under this extension let r = val.lenghtyFieldName1.lengthyFieldName2 |
@mageshb Not yet - for now you'll have to do something like let r = ((val
).lengthyFieldName1
).lengthyFieldName2 using parens to keep dot always next to expression on left side. |
@TheMatten on the topic of enhancements to As I've been implementing the I haven't yet been convinced that splitting |
I wrote a post on this topic some time ago. It describes all the known approaches (if something was left out, please let me know) and compares them. The one you've outlined is basically the worst (after having no polymorphic update at all).
There is the bad type family approach, the good functional dependencies approach and the novel
Please swap the names then, many people are used to
Why?
Having data Person = Person
{ name :: String
, age :: Int
} deriving Show
data Company = Company { owner :: Maybe Person }
deriving Show with monolithic incOwnerAge company = company{owner = fmap (\y -> y{age = succ y.age}) company.owner} with split incOwnerAge company = company.just.owner { age = succ age } (note sure if
Even just treating sum-types as set-only is already useful and doesn't require any indications. And having a way to indicate something is only a matter of settling on syntax: just about 500 comments on a proposal and you're done. |
Nice, thanks for this! I somehow lost track of it at the time, but this looks very comprehensive and is really helpful as a comparison of the approaches.
I haven't thought about it as much recently, but I generally agree. Someone should write a proposal to use the FunDep approach. Any volunteers? Or maybe I'll get to it eventually...
Well, it's a complexity trade-off; dropping type-changing update gets you simpler inferred types. It's not completely obvious that having two
I think you'd need general modification syntax for that (something like Or you could use incOwnerAge company = company & #owner % _Just % #age %~ succ Once you get beyond simple (nested) field selection and update, I'd suggest using lenses/optics directly, rather than trying to extend record operators with pseudo-fields like FWIW I consider |
What I really do not understand, and should be found here, in case others like me read this to find the reasons: Why in the world wasn’t another symbol chosen? Why the (unspaced) dot (.), of all things? Why force significant space characters where a simple typo of a literally invisible single space completely changes the meaning of the code? Why make Haskell now literally impossible to write with a pen on paper, where one can’t tell if the space is just space or a space? I seems the reason for releasing something that is known to be at best a stopgap, is that the alternative would be to keep bickering forever. I don’t know what that holdup was, but that argument is fallacious because ignores the utter triviality of solving the above problem: The weirdest thing is, how this elephant in the room is never even mentioned anywhere I read. It cannot be that nobody but me ever thought of this, but it can also not be that everyone orchestrated to make even naming that solution a taboo like speaking of the devil. So… did people like me miss an important memo? In that case, please, add that memo to the answer for the above. This would help people like me a lot with understanding how any of this can be a reasonable best choice even for a temporary stopgap, especially in the face of the dangerous threat of falling down the local energy minimum of "good enough", where the motivation left to actually do it right is too weak to fix it properly. As they say: Nothing more permanent in the world, than a kludge. ;) Or, alternatively: I, for one, am already preparing a patch for my own installation of GHC, named RecordSelectorSyntax, that treats the selector as just a normal function and lets you define the symbol to use by importing it under a different name. E.g.:
TL;DR: In any case, the way this was communicated down the stream, to us end users, was a train wreck of KDE4.0 or Firefox Fenix proportions. If I didn’t know SPJ et al to have such a highly respectable track record of wise decisions, I’d think they’d lost all their marbles and gone full PHP.JS. ;) |
AFAIK the dot is used because of a few reasons:
Furthermore, everyone using their own selector would make reading other people's code potentially pretty confusing. And if ALL syntax can be switched for whatever, that would potentially make reading other people's Haskell code a nightmare, because you have to keep checking the syntax is still what you think it is ALL the time. All in all, I'm pretty happy with the implementation and would like to enforce the use of spaces around operators anyway :) Also helps with readability IMHO. |
@navid-zamani this proposal was accepted over a year ago, and is released already (9.2.1 October last year). So I think commenting here is not the place. Suggest you start a thread on Discourse. Contra your gloom-laden predictions, I haven't seen the sky falling since October:
Most of us use a computer to write Haskell code. There was already in H98 several places where spaces around
How are you distinguishing those with your quaint technology?
Probably every possible symbol is already used in somebody somewhere's code. So would you like to explain to them why you want to break their code. The advantage of And what @Vlix said. |
Thank you for these very reasonable and level-headed replies. The Haskell community proves to be the best again. :) (Now all I wish is for the QWERT* layouts to die a deserved death. … Using NEO 2.0 over here, think German Dvorak but a thousand times better. ;) -- @AntC2: „Contra your gloom-laden predictions, I haven't seen the sky falling since October“ … Well, piecemeal tactics (what I mistranslated as “slippery slope“) always sneak in unnoticed, bit by bit, until it’s too late., That was kinda my point there. … But you followed it with good arguments, so in return for them, that “This is fine“ emotion-laden statement is forgiven aswell. :) |
For my projects I always add an instance that lifts the HasField typeclass to a Maybe. Do people think it is a reasonable idea to add this to the standard library? In the extreme case it can even be done for any functor. instance HasField (field :: k) r t => HasField field (Maybe r) (Maybe t) where
getField = fmap (getField @field) Although perhaps a different operator is warranted. |
This is a tricky one. I also do this for some functors (in particular |
@ocharles you are right to be suspicious instance HasField (field :: k) r t => HasField field (Maybe r) (Maybe t) where
getField = fmap (getField @field) works for |
Requiring that you have a |
@parsonsmatt I believe there was a fair amount of discussion of that, for example at #286 |
@phadej Yea, indeed. I was only considering https://hackage.haskell.org/package/base-4.16.0.0/docs/GHC-Records.html#t:HasField |
The proposal has been accepted; the following discussion is mostly of historic interest.
We propose a new language extension
RecordDotSyntax
that provides syntactic sugar to make the features introduced in theHasField
proposal more accessible, improving the user experience.Rendered