Conversation
|
It'd be a shame to remove I like the idea of making partial fields safer and getting A thought that someone else brought up recently: could we use I'll continue thinking about this and look at the code, but I'm away on holiday for the next couple of weeks, so may not have much availability for a while. |
a90168e to
54fbe68
Compare
The timing is indeed a bit unfortunate, but it was laying there for a couple of months (and my other PRs are still not merged). I'd like
I added prisms for constructors having up to 5 fields (should take care of 99% of cases).
Right, it's a bit problematic. I think the best way would be to offer generic affine traversals as a function, but not integrated as labels to avoid the confusion after we integrate with Also, it's better to not encourage usage of partial field selectors as they frankly suck. |
0b895d8 to
51f5f08
Compare
c83b28d to
76d7b00
Compare
|
Ok, I created |
ca44670 to
9a7959e
Compare
|
I added support for type changing updates. Only one variant as they compile faster than type-preserving variants from Inference from Sλ> data X ph a b = X1 String a Int | X2 b deriving (Show, Generic)
λ> x1 :: Prism (X ph a b) _ _ _; x1 = #_X1
<interactive>:46:24: error:
• Found type wildcard ‘_’ standing for ‘X b1 b2 b’
Where: ‘b1’, ‘b2’, ‘b’ are rigid type variables bound by
the inferred type of
x1 :: Prism
(X ph a b) (X b1 b2 b) ([Char], a, Int) ([Char], b2, Int)
at <interactive>:46:1-39
To use the inferred type, enable PartialTypeSignatures
• In the second argument of ‘Prism’, namely ‘_’
In the type ‘Prism (X ph a b) _ _ _’
In the type signature: x1 :: Prism (X ph a b) _ _ _
<interactive>:46:26: error:
• Found type wildcard ‘_’ standing for ‘([Char], a, Int)’
Where: ‘a’ is a rigid type variable bound by
the inferred type of
x1 :: Prism
(X ph a b) (X b1 b2 b) ([Char], a, Int) ([Char], b2, Int)
at <interactive>:46:1-28
To use the inferred type, enable PartialTypeSignatures
• In the third argument of ‘Prism’, namely ‘_’
In the type ‘Prism (X ph a b) _ _ _’
In the type signature: x1 :: Prism (X ph a b) _ _ _
<interactive>:46:28: error:
• Found type wildcard ‘_’ standing for ‘([Char], b2, Int)’
Where: ‘b2’ is a rigid type variable bound by
the inferred type of
x1 :: Prism
(X ph a b) (X b1 b2 b) ([Char], a, Int) ([Char], b2, Int)
at <interactive>:46:31-39
To use the inferred type, enable PartialTypeSignatures
• In the fourth argument of ‘Prism’, namely ‘_’
In the type ‘Prism (X ph a b) _ _ _’
In the type signature: x1 :: Prism (X ph a b) _ _ _Inference from Tλ> x1 :: Prism _ (X ph a b) _ _; x1 = #_X1
<interactive>:47:13: error:
• Found type wildcard ‘_’ standing for ‘X b1 b2 b’
Where: ‘b1’, ‘b2’, ‘b’ are rigid type variables bound by
the inferred type of
x1 :: Prism
(X b1 b2 b) (X ph a b) ([Char], b2, Int) ([Char], a, Int)
at <interactive>:47:1-39
To use the inferred type, enable PartialTypeSignatures
• In the first argument of ‘Prism’, namely ‘_’
In the type ‘Prism _ (X ph a b) _ _’
In the type signature: x1 :: Prism _ (X ph a b) _ _
<interactive>:47:26: error:
• Found type wildcard ‘_’ standing for ‘([Char], b2, Int)’
Where: ‘b2’ is a rigid type variable bound by
the inferred type of
x1 :: Prism
(X b1 b2 b) (X ph a b) ([Char], b2, Int) ([Char], a, Int)
at <interactive>:47:31-39
To use the inferred type, enable PartialTypeSignatures
• In the third argument of ‘Prism’, namely ‘_’
In the type ‘Prism _ (X ph a b) _ _’
In the type signature: x1 :: Prism _ (X ph a b) _ _
<interactive>:47:28: error:
• Found type wildcard ‘_’ standing for ‘([Char], a, Int)’
Where: ‘a’ is a rigid type variable bound by
the inferred type of
x1 :: Prism
(X b1 b2 b) (X ph a b) ([Char], b2, Int) ([Char], a, Int)
at <interactive>:47:1-28
To use the inferred type, enable PartialTypeSignatures
• In the fourth argument of ‘Prism’, namely ‘_’
In the type ‘Prism _ (X ph a b) _ _’
In the type signature: x1 :: Prism _ (X ph a b) _ _It can even change phantom type variables. |
1f98cac to
f92ccfa
Compare
|
Compile time benchmarks from https://github.com/arybczak/generic-lens/tree/perf-work (
1 constructor:
2 constructors:
3 constructors:
|
090569c to
21c4a08
Compare
I would prefer that to be an error rather than a (hard to spot in large) slowdown, if I forget to TH generate an instance. |
Right, I see your point. I'm just unsure how to solve it in a satisfactory way. I thought this PR will allow libraries to use labels with Perhaps we could add a |
|
@arybczak yes, that is great for me. But indeed, I'm quite sure there are people who would prefer having generic labels for everything on by default. I don't see a way to satisfy everyone (thus why I'm fine with cabal flag for my-pedantic-self). |
|
Yeah. That covers the case of libraries wanting to use generic labels with their data types and applications wanting to use generic labels in general (as if a specific library is missing the instances, orphans can be provided), but it doesn't work for libraries wanting to use generic labels with data types from other libraries not knowing about Ideally I wanted this to create a sort of compatibility layer for sane field handling with labels from now on until we get But I do see the problem of using generics silently if one forgets to define At least this is better than the cabal flag, since with a flag no library can use generic labels. I'll let it marinate for another week or so, maybe someone will have an enlightenment how to solve it gracefully. |
What about having a manual flag biased towards allowing generic labels:
That way users who really care about performance can set the flag at build time and guarantee the absence of generic instances. Of course they might hit the problem that some library they depend on uses a generic instance from another library they depend on, and the build will fail. But if they did explicitly ask for that...
I generally agree that flags shouldn't change APIs, but in this case we are really just saying we want to build in "hard mode" as an additional check on the build. And if that is explicitly requested by the person building the package, it seems okay to me. Really we want some way to (optionally) request a warning when a certain instance is used, which is occasionally requested as a GHC feature. |
|
@adamgundry I was thinking about a similar thing - manual flag, enabled by default and explicit note that turning it off is generally unsupported. That would render the flag unusable once/if libraries rely on it enough. On the other hand, the Choices... |
|
I think I have a middle ground solution. I added This gives more flexibility to application authors as they can enable this flag and patch/send PRs to affected libraries to simply derive @phadej @adamgundry what do you think? |
|
This looks satisfactory to me, I'll merge (this time for sure 😛 ) during the weekend. |
adamgundry
left a comment
There was a problem hiding this comment.
Thanks, I'm happy with this approach. I've made some suggestions regarding documentation.
I was initially worried that it is unclear under what circumstances it would make sense for libraries to provide GenericLabelOptics instances, and hence wondering whether the class is useful in addition to the flag. But I think it is fine to say they shouldn't normally bother, yet keep the class as an override mechanism so that an application developer can choose to build with -fexplicit-generic-labels and potentially define (possibly orphan) GenericLabelOptics instances.
Co-authored-by: Adam Gundry <adam@well-typed.com>
Co-authored-by: Adam Gundry <adam@well-typed.com>
Co-authored-by: Adam Gundry <adam@well-typed.com>
Co-authored-by: Adam Gundry <adam@well-typed.com>
Libraries shouldn't bother unless they themselves use generic labels with their data types, then it makes sense for them to derive these instances so that the library works with |
|
@adamgundry I applied your suggestions, I think we're good to merge this 🎉 |
While working on https://gitlab.haskell.org/ghc/ghc/-/merge_requests/2965 I extracted code from
generic-lens-litefor a perf test and made it fast at compile time (and fast at runtime with the above MR).Taking #359 (comment) into consideration I'm tentatively for removing
GeneralOpticLabel(and the pluggable incoherence it introduces) and provide generic field access.You may think I'm contradicting myself since I was against generic field access, but:
gfieldin under 100 lines of (modestly complicated) code with good error handling.setField(depending on GHC version), plugabble generic based access and TH.RecordDotSyntaxwithout looking at TH and reading a guide inOptics.Label, it's a win.Right now I'm thinking of
gafieldtoOptics.AffineTraversalfor partial fields (similar effort asgfield).Integrating both into LabelOptic so that you get aLensor anAffineTraversaldepending whether the field you want is partial or not.So then with #359 we'd have a better story:
type changing updates andspeed)Lensthat might fail withHasFieldortype error if the type has Generic instance.AffineTraversalLenswithHasFieldmachineryno matter if the type has a Generic instanceor type-changingLensif the type has a Generic instance (and type-changing is needed).Though it's not yet clear how expensive is checking if a field is total.That would also fix #140.
Thoughts? @phadej @adamgundry