-
Notifications
You must be signed in to change notification settings - Fork 15
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
Add HasField instances for tuples to allow tuple-indexing #143
Comments
Thanks for taking the time to develop this idea into its own package! |
https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0170-unrestricted-overloadedlabels.rst allows numbers as labels ( |
No problem. I just don't want to upload it to Hackage, since publishing a library which only consists of orphan instances is probably bad for my Karma :) Orphan instances are one reason why I think it shouldn't be a library in the long run, the other one is that it fixes a small syntactic inconvencience in Haskell, and adding a dependency and an import is the comparatively bigger inconvenience.
Thanks, I hadn't seen this proposal. I could have phrased the passage better by saying that my personal preference is to use |
Thanks for writing such a thoughtful proposal and putting effort into it! Generally, I agree with the idea of making tuples more ergonomic. I myself experience the pain of switching from There's a general sentiment that you shouldn't rely on tuples too much, and you're encouraged to introduce custom records instead. And I agree with it. But in some cases, it's not that easy. I can imagine providing some generic tuple interface for FFI or e.g. like the one from I don't have an opinion on
I'm personally not against orphan instances but I don't know the best place to put them. Similarly, I wouldn't mind moving |
Sure. Here is my system configuration: $ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Address sizes: 39 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 9.4.2 And here are the timings for all iustances up to 62 tuples: $ time ghc src/Data/Tuple/Fields.hs
[1 of 1] Compiling Data.Tuple.Fields ( src/Data/Tuple/Fields.hs, src/Data/Tuple/Fields.o )
real 0m6,918s
user 0m6,676s
sys 0m0,253s If I remove all instances above 16 tuples, I get the following times: $ time ghc src/Data/Tuple/Fields.hs
[1 of 1] Compiling Data.Tuple.Fields ( src/Data/Tuple/Fields.hs, src/Data/Tuple/Fields.o )
real 0m0,290s
user 0m0,255s
sys 0m0,028s |
That compilation hit shouldn't be visible to end users if this is in I'm a fan of virtual fields like this - I think it's one of the better uses of the feature. I'm in favor of I'm ambivalent about where the instances go. Orphans are regrettably common with some of the core datatypes, but as long as this is exposed in the |
Am I missing something or why can't the instances be declared right next to the |
I don't think this goes far enough to justify it, it is less convenient than lens' _1 etc and you can even achieve . with RebindableSyntax setting getField as ^. and setField as %~ (giving you even more from lens over this). It is also inconvenient for hlist et all as you are getting the type level string "_1", not the natural number 1. FWIW you could provide these instances via GHC.Generics to Generically, then use DerivingVia both for tuples and as an opt-in for users for their own types. But you still have the downside of a string that presumably needs the _ extracting then the tail parsing before you can recur on it as a natural. |
A "base package" or "core library" should provide:
A "base package" or "core library" should not provide:
|
It doesn't. Changing
Agreed, I think the instances can be defined as non-orphan in |
Haskell Report says "The Prelude and libraries define tuple functions such as zip for tuples up to a size of 7", and I think this is a reasonable limit.
I'm not particularly worried about orphans here, a single dedicated library with instances can work fairly well, even in the long run. |
I think this is an interesting idea, but I don't have any experience with I would actually suggest to upload |
Just a note that the CLC is (still) not aligned on what base should or shouldn't provide. |
If there's no particular reason not to permit them, I really think this should be explored, even if it means this proposal (or a modified version) takes longer to come to fruition. |
@BinderDavid how would you like to proceed? I see that you've uploaded https://hackage.haskell.org/package/tuple-fields, so my suggestion would be to announce it widely, gather feedback, gain adoption and return to this proposal in a couple of months or so, hibernating it in the meantime. But as a proposer you have a right indeed to pursue the CLC decision as is. |
class HasField x r a | x r -> a where
getField :: r -> a But the accepted GHC proposal 158 suggests changing it to class HasField (x :: k) r a | x r -> a where
hasField :: r -> (a -> r, a)
getField :: forall x r a . HasField x r a => r -> a
getField = snd . hasField @x And then a fresh GHC proposal 583 changes it yet again to class HasField x r a | x r -> a where
getField :: r -> a
class SetField x r a | x r -> a where
modifyField :: (a -> a) -> r -> r IMO this is a strong evidence that the design of @BinderDavid please suggest how would you like to proceed within two weeks, otherwise I'll mark the proposal as dormant. |
My impression is that the first proposal of record dot syntax envisioned one typeclass W.r.t to this proposal: I have uploaded the package tuple-fields to Hackage and will keep it running with any future changes to the
I think we will see how prevalent the use of |
I like the feature but I think it's too early to include this change in I wouldn't want Besides, since the design of relevant GHC extensions is still experimental, I'm not too comfortable with increasing the burden of GHC developers to update hundreds of lines of orphan instances when |
Thanks @BinderDavid, hibernating. |
TL;DR
Add instances of the following kind to base and make them available via the Prelude:
In order to allow access to tuple elements using record dot syntax like this:
Motivation
Accessing elements of tuples other than 2-tuples is currently cumbersome, since the Prelude defines
fst
andsnd
functions only for 2-tuples. We have to pattern match on n-tuples explicitly in order to access the elements of the tuple. Other languages provide some form of tuple-indexing, which makes these n-tuples much more convenient. With the OverloadedRecordDot we can use this newly available mechanism to enable convenient tuple indexing as well.What about another name for the field?
In an ideal world we could use the syntax
(true, "hello", 42).2
without the underscore. This is, for example, the syntax that Rust uses for tuple-indexing: Rust by Example. As far as I can tell, this doesn't currently work since this expression cannot be parsed. If we want this syntax instead, then the parser/lexer would have to be changed, and this couldn't be a CLC proposal but would have to be turned into a GHC proposal.What about Lens / Optics
Both the Control.Lens.Tuple and the Data.Tuple.Optics modules provide accessors using the same naming scheme.
With both Lens and optics you can use the syntax
(1,2) ^. _1
.I think using the Lens/Optics libraries is not a proper solution to the basic usability problem of tuples outlined above.
I just want to make it easier to access fields in a tuple, not use a big library to permit abstraction over access into nested data structures which requires additional imports and dependencies. If these instances are exported in the Prelude, then I never have to add any imports, and can just use the record dot syntax (if I have OverloadedRecordDot enabled). Record dot syntax is also much more newcomer friendly. If we do want to use the full expressive power of Lens/Optics, then this proposal actually provides an easier onboarding ramp, since the Lens and Optics libraries use the same names for tuple fields. The HasField instances and the Lens/Optics accessors can also peacefully coexist in the same codebase.
Can I try it out?
Yes, you can add a dependency of my prototype implementation
tuple-fields
on GithubWhat about Solo?
For consistency reasons, Solo should also get its instance:
Should instances for all n-tuples be provided?
Currently the largest tuples are 62 tuples. In https://github.com/BinderDavid/tuple-fields/blob/main/src/Data/Tuple/Fields.hs I added all instances for all tuples. The Lens and Optics libraries don't support field access for all these tuples. I guess that the reason might be due to excessive compilation time? Adding all instances would be the more consistent choice, but I don't know the details of how that interacts with the complexity of instance search.
What about unboxed tuples?
Adding the corresponding instances for unboxed tuples is currently not possible.
The following instance:
currently results in the following error:
This is because the
HasField
typeclass is currently not representation-polymorphic enough.Discussions about making the typeclass more polymorphic are discussed here https://gitlab.haskell.org/ghc/ghc/-/issues/22156 and here https://github.com/adamgundry/ghc-proposals/blob/hasfield-redesign/proposals/0000-hasfield-redesign.rst#recap-planned-changes-to-hasfield Since adding these instances is currently not possible, adding them is out of scope for this proposal.
Backwards Incompatibility
I don't currently see how this could break backwards compatibility in any way. Afaik, as soon as the OverloadedRecordDot extension is enabled, the lexing rules around the
.
symbol change so thatfoo.bar
without whitespaces can only be used for field access. So there should be no conflicts with existing uses of the tuple accessors from the Lens or Optics libraries.The text was updated successfully, but these errors were encountered: