-
Notifications
You must be signed in to change notification settings - Fork 17
Description
TL;DR
Add instances of the following kind to base and make them available via the Prelude:
instance HasField "_1" (a1,a2,a3) a1 where
getField (x,_,_) = x
instance HasField "_2" (a1,a2,a3) a2 where
getField (_,x,_) = x
instance HasField "_3" (a1,a2,a3) a3 where
getField (_,_,x) = x
In order to allow access to tuple elements using record dot syntax like this:
(true, "hello", 42)._1 == true
(true, "hello", 42)._2 == "hello"
(true, "hello", 42)._3 == 42
Motivation
Accessing elements of tuples other than 2-tuples is currently cumbersome, since the Prelude defines fst
and snd
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 Github
What about Solo?
For consistency reasons, Solo should also get its instance:
instance HasField "_1" (Solo a) a where
getField (Solo x) = x
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:
instance HasField "_1" (# a1,a2,a3 #) a1 where
getField (# x,_,_ #) = x
currently results in the following error:
Main.hs:17:24: error: [GHC-83865]
• Couldn't match a lifted type with an unlifted type
Expected kind ‘*’,
but ‘(# a1, a2, a3 #)’ has kind ‘TYPE
(TupleRep [LiftedRep, LiftedRep, LiftedRep])’
• In the second argument of ‘HasField’, namely ‘(# a1, a2, a3 #)’
In the instance declaration for ‘HasField "_1" (# a1, a2, a3 #) a1’
|
17 | instance HasField "_1" (# a1,a2,a3 #) a1 where
| ^^^^^^^^^^^^^^
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 that foo.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.