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
Relaxing HasField
constraints (under review)
#515
base: master
Are you sure you want to change the base?
Conversation
Generally I like this proposal
Well, that is already true for Num, Eq, Ord and every other class. You can define incoherent instances in different (sibling) modules. It's no different here, I think, is it? So it seems cruel to count this as a downside, since we accept it for all other classes with manual instances. |
True, we could take the position that However, my position on this is influenced by a change I'd like to make but have not yet formally written up as a proposal: that we should automatically solve |
Hopefully @adamgundry's comment has responded adequately to @simonpj's. What's next for this? I note it's been mostly quiet for the two weeks since submission - should I wait a bit longer? |
If you think that it's quiet because it just wasn't looked at by the community in general, maybe beat the drum on forums/reddit/mailing lists and solicit more input. If you think it's quiet because there isn't much too comment, and possible issues would have brought up, you can formally submit for committee consideration. That is often a forcing function to get committee members to look at it more closely. |
Thanks @nomeata! Will have a think tomorrow morning 👍 |
After being initially skeptical, I'm in support after thinking about the ramifications. |
Good to hear @goldfirere! Could you share your initial reservations anyway? I'd be curious to hear them 😃 |
Well, I was wrong: I was most concerned by strange user experiences. For example, your |
Thanks for expanding, @goldfirere! I would like to now submit this to the committee for consideration. |
ping @nomeata now you're back ^^^ |
HasField
constraintsHasField
constraints (under review)
Hi! I've finally read through the proposal - thanks for your patience. Perhaps I'm biased as this is a problem I've run into myself, but I'm very in favour of this proposal. I'll recommend it to the committee! |
The proposal says
But I don’t get it, and I don’t understand the example. I am confident that the analogy with IP is a red herring, because implicit parameter make (in effect) local instance declarations, and there is none of that here — only top level ones. So it seems there is more in common with ordinary overlapping instance decks. In the example,
Surely the instance decl in N simply overlaps (completely in fact) the implicit instance decl in M (the built-in solver one), so I’m baffled here. Generally I think the proposal should make it clear that the behaviour is JUST AS IF the built-in solver-magic instance declarations were written by the user in the module defining the type EXCEPT that they be once invisible if the field is not imported. (That last part is a bit magic.) OH. Maybe in the example the point is that |
Generally speaking, I’m in support of this proposal. |
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.
It seems to me that the question of whether the compiler may assume coherence or not is rather important here. The getField
method is one that I expect to lend itself pretty well to specialisation (as it's virtually always called with a static field name). I don't think that GHC is currently capable of doing specialisation for classes that it doesn't assume to be coherent, so the performance implications may be important.
On the other hand, I may be wrong, maybe the standard getField
will simply be inlined, no need for specialisation. I don't know. At any rate, this discussion should feature in the Alternatives section.
That is indeed the point. More broadly, the ability to "hide" instances based on imports/exports is what (I think) makes it similar to local instance declarations for IPs, though of course for
Yes. I think it would be reasonable to say that GHC is entitled to assume Here I suppose the analogy for IPs breaks down, because for IPs we really do expect different values depending on local scope, and it would be manifestly unreasonable to specialise. |
Perhaps you can modify the proposal to make it impossible for a reader to misunderstand in the way that I did. At least point out the highly-significant selective import; and remind readers that the instance is only in scope if the field selector is. |
Some thoughts
I will note that the built-in instances aren't really expressed as instances; they are code. So implementing these overlap rules will be fiddly.
|
I'm generally in favour; would like to see the above clarifications executed though |
Co-authored-by: Arnaud Spiwack <arnaud@spiwack.net>
One question that seems unaddressed here is optimization. Right now, if the optimizer has Relatedly, Sorry for being late with this negative analysis, but it didn't occur to me earlier. |
Thanks for the feedback @simonpj and @goldfirere! I see @adamgundry has already responded to some points, but there's clearly some work left to do here. Some of this may be beyond my current abilities, but I'll try and do what I can and work with Adam to get all those open questions answered 👍 |
@ocharles or @adamgundry, are you by any chance attending Haskell Symposium? If so, would you like to present this proposal at the GHC Proposals session? |
@goldfirere I agree that the proposal could be clearer on the optimization point. I think refusing to specialise
I'd appreciate feedback on whether this is acceptable. If the risk of users accidentally introducing incoherence is too great, we could imagine requiring a modifier/pragma on potentially-problematic
Alas I'm not able to make the Haskell Symposium this year. Good idea to organise such a session though! |
Another possible approach occurred to me: we could have an on-by-default warning about potentially-problematic |
But I'm worried -- with the possibility of cross-module inlining, a key part of GHC's optimization pipeline -- that there will be no good way for even expert users to know whether they might be bitten by the incoherence. Module boundaries pretty effectively cease to exist in the optimizer. It makes for a strange UX, but maybe this would work: If a module does not export a field name and never uses a This little optimization around class constraints causes us some pain (both here and elsewhere, though I don't have a ready link to the "elsewhere"). Do we know that it makes a difference? That is, perhaps we could just disable it and then see how much of a slowdown we get. |
Whatever happened to Richard’s concerns? Could they be addressed? |
I’ve reassigned shepherding this proposal to @angerman. |
After reading (and trying to understand this proposal), as well as reading the comments:
|
Thanks for the quick review @angerman! There are two options on the table here:
The current proposal text describes option 1, but I'm leaning towards option 2.
Assuming we choose option 2, my preferred suggestion is that we should have an on-by-default warning (maybe even a "severe" warning that is an error unless explicitly disabled?) that tells the user the instance might lead to incoherence and they are proceeding at their own risk. (Obviously the exact text would need to explain a bit more!) This is not massively different to As @goldfirere explained, it is difficult to characterise exactly when an instance will cause problems in practice (and perhaps in the future we will identify a subset of instances that are safe, and hence not warned about, e.g. where the module doesn't use or export any conflicting fields). Thus I think it is reasonable to have a warning, which can be refined if necessary to not warn about safe cases.
No. This proposal allows the user to define strictly more instances (those that are currently prohibited by a check in GHC). Thus I don't think it can break existing code.
Option 1 seems likely to reduce specialisation opportunities, and hence seems like it could be quite bad if
We don't currently require a language extension to use |
This looks like some serious discussion is still happening. Maybe label it as needs-revision (which should not be thought of as a “bad” thing), and re-submit when refined? |
I had another look. The proposal plans to lift four restrictions, but it is only supported by two examples. That's a bit thin on motivation. At least it'd be good to have an example supporting each restriction-lifting. More fundamentally I'm trying to understand the overlap/incoherence issue. Take the main example:
The intent is that if But what about the functional dependencies? If we have For ordinary instance declarations GHC avoids this problem by checking
then GHC complains
and rightly so. So I'm a bit stumped:
Some of this might go away if the instances looked more like this:
But then of course the fundep on All this seems pretty fundamental. I must be missing something |
That's what @adamgundry mentions as behaving as
No. Special code solving
matchHasField dflags short_cut clas tys
= do { fam_inst_envs <- tcGetFamInstEnvs
; rdr_env <- getGlobalRdrEnv
; case tys of
-- We are matching HasField {k} x r a...
[_k_ty, x_ty, r_ty, a_ty]
-- x should be a literal string
| Just x <- isStrLitTy x_ty
-- r should be an applied type constructor
, Just (tc, args) <- tcSplitTyConApp_maybe r_ty The implicit |
Ah I see, thank you. The Note does not express it this way, but it's exactly asif we had implicitly generated
That is, in the implicitly-generated instance, apparently, the last parameter is always a type variable. So we never get any useful fundeps from it, so neglecting them is fine. But then why don't we do write the user written instances like that too:
That would render the fundep essentially powerless, which consequences I don't understand. After all, it seems that the fundep on Ah -- I suppose that today if you have Returning to the original case, if you have
which the built-in solver solves using (I1) yielding the perplexing result
This does not look good to me. Maybe we should insist that user-defined instances also always have a type variable as the last parameter? (Or use a type family instead of the last parameter.) |
I agree that we can get strange behaviour if code uses both the implicitly-defined But in the motivating examples, we typically don't want the implicitly-defined instance, because Perhaps we could introduce such a mechanism. But the only options I see are having the semantics depend on module exports, which @goldfirere aptly called "very spooky action at great distances", or having a syntactic marker that a particular datatype/field shouldn't have
|
If the |
I don't think the built-in solver will solve it. It should only ac if there are no matching user-defined instances. I don't see how to construct that example using orphans, as you start from knowing I2 exists, so it's there already. And if TL;DR built-in solver should be the fallback and user-defined instances should always take precedence. |
Given the ongoing discussion, and @adamgundry's preference for Option 2, I'll take @nomeata suggestion to change this to needs-revision. |
* Introduce severe warning when a potentially incoherent instance is used * Clarify proposed change specification around overlapping instances * Allow optimiser to assume canonicity for HasField
Thanks everyone for your input. I've amended this proposal as follows:
I'd welcome further feedback. |
Thank you @adamgundry, very clear write up. (Which shows your design decisions.)
Yes I think that's a sensible design choice. I agree the interaction between
I want to say the explicit instance is
in which I do need explicit [**] I don't think this needs the 'Dysfunctional Instances' proposal that you link to. Merely: for the purposes of overlap-check, ignore FunDep target positions. (Yes this gets ugly if there's multiple FunDeps -- but that doesn't apply with
That would be legal with
Hmm. That seems sad. From your examples given (though perhaps you have gnarlier examples in mind), I think the 'magic' needs four things that apply for both explicit and implicit instances of
IOW I'm anticipating FunDeps for I'm thinking the [***] If it's any help, I've already amended Hugs to apply these rules for instances in general -- including the gnarly cases of multiple FunDeps ... ... and specifically for some very fancy overlap of instances over TRex records. Addit: I should say the other key consideration for relaxing FunDep rules is that |
I said above
Would it be possible to address this? |
`Several points.
But this directly contradicts the user manual entry for HasField. Which is right? (Answer: the proposal is; I just looked at This matters, even aside from this proposal. Earlier I said
But actually, with the above implicit instance, these omissions make sense: given
Then I think Ditto what if you add
Then I think
Please give the class for
What does that mean? Give an example. I think it's something like this:
Do these contradict? No they do not; there will be consistency-check failure. Example without special purpose classes:
This module is accepted today. So, what does the sentence mean? I think it's simply the case that there can be no consistency failure; i.e. you can just delete the sentence.
If you have
That matches Now, if later we discover
Now If
Now both None of this was obvious to me. I strongly urge that you work this through in the proposal to demonstrate the consequences.
But is there anything incoherent? We simply behave as if the implicit instance existed, with GHC's existing rules for selecting instances, don't we? Or are you suggesting that the instance lookup rules for |
@simonpj (I think you're being harsh: this stuff is a tangled mess inside GHC, whether or not we try to thread
I'd hope not (or at least I hope no attempts at usage sites will be accepted): because the instance heads overlap in no substitution ordering.
Then I'd expect the instance decls to get accepted, but usage sites to be problematic. (As I said above, this is really nothing to do with
We should treat all
Hmm. I'd say all the non-obvious behaviour is already there with existing FunDeps/overlap. I'm not sure
You've hinted |
IIRC the motivation was "a bit thin" for those restrictions in the first place. They're there out of an abundance of caution. We now have
[**] I'd better avoid "coherence" as a term: that has already a specific sense wrt instances & overlaps. So to take those four restrictions:
|
In the overloaded record fields proposal, there is a set of limitations as to when a user can declare custom
HasField
instances. In this proposal we relax these restrictions, allowing users to defineHasField
instances when they were previously unable to.Rendered