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
Prevent Incoherent Instances #279
base: master
Are you sure you want to change the base?
Conversation
Looks like you wrote a markdown document but saved it as rst --- the formatting is all wonky! |
Should be fixed now. |
For what it's worth, the ghc-proposal process now supports markdown, so in the future you need not translate one to the other :) |
The proposal doesn't even mention that problems starts only when you have And quite often you can make helper classes to avoid FlexibleInstances, IMO proposal should discuss that in the alternatives section. module Foo were
instance Foo [a]
class Foo a where
instance Foo [a]
class FooArr b => Foo (a -> b) where module Bar where
data Bar
instance Foo Bar -- ok
instance Foo Int -- orphan
instance Foo a -- needs UndecidableInstances, overlaps instance Foo [a]
instance Foo (a -> Bar) -- flexible instance, overlaps instance Foo (a -> b)
instance FooArr Bar -- ok, induces Foo (a -> Bar) instances
instance Foo (Bar -> a) -- flexible instance, overlaps Foo (a -> b) |
Thanks @tysonzero, I'm having trouble putting my finger on what you're trying to address. (The Could your Motivation section actually include the critical bits of code from the Pastebin examples, together with a narrative for what goes wrong/your explanation for what you think is wrong about it? In general, GHC is far too willing to accept instance decls that taken together would make an inconsistent program theory; but take the risk that the usage sites won't ask for an instance at the inconsistent/overlapping combo of types. Contrast that Hugs has much tighter validation of instance decls; which means you don't get inconsistent program theory; but there are programs you want to write that it won't accept. It would be good to link this proposal to the applicable tickets raising issues to do with incoherence -- there's gazillions of them. Can you show how/why such programs are rejected; and just as important show how very similar programs are accepted OK. @phadej makes an important point that the difficulties start with Is the
|
You also have an identical set of problems with MultiParamTypeClasses. I originally had a more restrictive rule that you must always own the top level type constructor which would fix the FlexibleInstances problem, but then I noticed I still needed a more powerful solution to the MultiParamTypeClasses problem, at which point I noticed that this generalized solution also solved precisely the problems introduced by FlexibleInstances, so I relaxed the rule to be the same for both. The advantage of allowing users to not have to define a helper class is to avoid extra namespace bloat that doesn't really add any additional value, as If it weren't for MultiParamTypeClasses I would probably be more in favor of requiring the helper class, but since the One clear indication of how tightly linked the FlexibleInstances problem and the MultiParamTypeClasses problem are, is to look at how you can always write any multi param type class as a single param type class over a tuple, and how you can always rewrite a flexible instance using helper typeclasses which may or may not need to have multiple parameters. I should include more of the above in the original proposal though, as I originally had the same thought as you. |
Actually no (but this is a common misconception). MultiParams give no problems with
Yes-ish, but without repeated tyvars within the tuple, you can't write the instances you want. |
Yeah good idea. Basically I am only defining lawful So basically it all comes to the idea that currently GHC allows incoherent code to compile without warnings or errors, and I want to make it so that you need to explicit tell GHC you are ok with incoherence for that to happen.
Hmm I suppose it isn't! This is probably a more reasonable approach to go than a new keyword, so I will most likely edit the original proposal, although I think it needs a bit of polish:
It would be nice if you could omit the definition and ghc would be fine with it knowing that it is unreachable. Similar to the following Agda:
|
Ah that's a good point. Due to the restrictiveness of only using MultiParamTypeClasses without FlexibleInstances I don't think this makes me want to change the proposal much, but I will have to keep this in mind when deciding how to phrase things and talk about alternatives and such. |
A couple more ideas to include in the Alternatives section:
I think you should consider how that interacts with The Instance Apartness Guards proposal has a lot of material about interaction with FunDeps. Also some of the objections to that proposal would get raised here. (For example, the helper classes @phadej suggests.) And before you put in a heap more effort here, something I learnt from experience with that Instance Guards: who's going to implement this proposal? If you don't have the skills/knowledge, you're reliant on GHC HQ. Then my take is that nobody at GHC HQ has any appetite for further work on Overlaps or FunDeps or Without wanting to put words in anybody's mouth, I think the claim would be that GHC can handle your examples with helper classes and Closed Type Families. Yes that would give namespace bloat. That pain is not severe enough for the additional compiler complexity in a makeover for the overlapping/incoherent instances machinery in GHC -- which is already a dog's breakfast, by all accounts. |
Hmm it seems like those are specifically to deal with overlapping instances? The overlapping instances part of this is honestly an afterthought, I never use them personally. I just want the coherence issue in GHC fixed, and to try and prevent the occasional problematic instances I have seen appear in libraries.
Yeah perhaps I was a little bold in my initial wording of that. My point was mostly just that this solution seems to generalize to both of these cases quite well. Although if some of this extra nuance does break the proposal in some way then that would of course be very important information. I'm not trying to get rid of MultiParamTypeClasses or anything like that.
I mean my approach is not trying to express new things that we can't already express. So I'm not particularly concerned with what new features we can add to express these instances. I'm trying to fix what I consider to be a pretty significant bug, namely that you can cause incoherence without any warning and with extensions generally considered to be benign. Perhaps most consider coherence to be of fairly low importance, but I had assumed otherwise when making this proposal. |
I'm now very confused as to your motivation. I suggest you write out the Motivation section with examples before making any more proposals. Why do you think this is to do with anything else than overlapping instances? The I'd say that far more realistic use cases are people who use and understand overlaps, and never use
Then please give examples of those, and clearly describe why they're "problematic". It's true that for some libraries, because you can't intrude into the instances they declare, you do end up with potential incoherence. That is all and only because of overlap. Please explain with an example:
You perhaps don't realise (though I said it already) that just declaring instances that overlap (and without |
I linked this example in that section which I thought very clearly demonstrated the problem. I never used I apologize if I sound frustrated but I don't know what I need to do to make it clearer. The |
I don't think there's any incoherent instance resolution happening in that example at all. The result is obviously bogus, but that's because In The only constraints that need to be solved for in None of the above involves overlapping or incoherent instances. If you want to trigger overlapping instance errors, you need to "delay" instance resolution until there really are overlapping instances in scope. In this case, "delaying" just means changing the types of -- In OrphansFoo:
foo :: Ord (Tuple Foo b) => b -> b -> Set (Tuple Foo b)
-- In OrphansBar:
bar :: Ord (Tuple a Bar) => a -> a -> Set (Tuple a Bar) -> Set (Tuple a Bar) That moves the choice of |
That's the exact definition of coherence. Coherence means that for a given typeclass and type(s), you will always resolve it to the same instance dictionary within the same program. So the fact that two different
Right, if you look at the "adjusted type checking" part of the proposal I suggest that GHC needs to do exactly that, so that we can keep coherence and either get errors or correct and coherent overlap. |
That's 60 lines of code without a skerrick of comment or narrative. a) people might not wade through it; b) they might not see any "problem"; c) they might see a problem that is not the problem you think is "very clearly demonstrated"; d) If GHC accepted it without error or warning and it runs without throwing an exception, then there can't be a problem. Thank you to @nil-ptr for making more effort than I did. The answer seems to be d).
Then please define what you mean by "incoherence" (in the Motivation section -- you've now explained it in the reply to @nil-ptr, why did it take this much asking?). It's clearly not the same as what GHC's BTW you do have overlapping instances for
Exactly as if this were a bug report: describe the expected behaviour (in English); give a minimal piece of code that exhibits the "problem"; say what GHC does and why that's 'wrong' in your opinion.
Does it? Oh, so I need to run this code and inspect the output to decide whether it exhibits any problem. Your 'bug report' should show that output. |
Ah. Well, we're using different definitions of what it means for an instance to be coherent then. Because to me every instance selection in that code is perfectly reasonable. But I think I get what you're actually after now: you want global instance coherence, correct? Or package/project-global coherence, anyway. |
Uhh... what? If
I honestly assumed there was a single agreed upon definition of coherence. The same one used in Type Classes vs. the World and the same one used in the top results when you search typeclass coherence. Although I see some disagree on the definition so I should have been more precise.
I mean it basically is. The incoherent pragma allows you to violate the above definition of coherence. It just turns out that there are other ways to violate coherence, orphans (which was known and detected by
Right. I should have been more clear with my wording. I don't use "OverlappingInstances" the intentional feature made up of What I want is for ghc to properly enforce that there is no unintentional overlap that leads to incoherence. Intentional overlap I am less concerned about as I do not use it, but I wanted to make sure the proposal didn't get in the way of those who do like intentional overlap.
Yeah alright that's a fair point. I came at this too much from a feature proposal point of view, when I should have treated it as a bug report first and foremost and a feature proposal as the solution.
There is a comment at the bottom with the contents of the set.
I was under the impression that is what everyone wanted/expected (except when using When I google "instance hiding" or "instance coherence" all the discussions care about it on a project-wide level, not on an a local level. I mean what's the point of local-only coherence? With only local coherence you can break |
the ghc manual agrees on the notion of coherence. c.f. I don't this proposal prevents it however! It just gives certain control over more granular warnings. I don't know if I'm particularly happy with the mechanism proposed for the warnings, which feels ad-hoc, complicated, and confusing. And I think that the discussion in the proposal itself could use some more clarity in establishing exactly what problem its trying to solve, etc. As it is it feels unformed. But as a general goal: warning about potential conflict in instances more thoroughly seems like a sound problem to attack. @phadej can you elaborate more clearly on the problem with flexible instances? e.g. the users guide warns about overlaps, but not flexible instances in this regard. can you provide an example that shows the same behavior with just flexible? |
Oh, there are regular requests/proposals for local-only instances. For example a module that does pretty You can somewhat achieve that local effect by exploiting orphan instances. But everybody knows that's flakey. (I'm playing devil's advocate here: those proposals disintegrate when you give some pointed examples of local vs exported functions that pollute instance resolution globally.) Addit: And because Scala does that with its 'implicits' -- as discussed in some of the articles caught in your "typeclass coherence" GHits. (It's debatable whether 'implicits' are a Good Thing in Scala, and anyway Scala typeclasses are different enough from Haskell that aping it would be cargo cult language design.) BTW when Odersky says "Haskell's type classes have a coherence requirement which states that a type can implement a type class only in one way anywhere in a program." that's just wrong. Haskell has no such stated requirement and GHC doesn't enforce it. Programmers in Haskell might want such a requirement, but they're in for disappointment. With H98, you get only one way to implement a typeclass per type, and that's maybe by design. But as soon as you go beyond H98 (
Here's a sensible use of instances that seem incoherent (but aren't
The first two
There might be for those who know the topic closely. Never the less, your proposal should give a definition (or at least reference one). Then the examples in the proposal can specifically say: this code is incoherent by that definition; and is to be rejected, by this part of the proposed change. |
There's not so much a problem with
The 'problem' is that there's no way to tell GHC: I want The far-and-away clearest way to demonstrate this is to run up Hugs. In Hugsmode you get Addit:
Ah, you mean "can give rise to incoherence" in the big red Warning at the end of that section of the Users Guide. But note that has really little to do with
|
I agree with @tysonzero on the opinion it is a real problem that global coherence can be compromised silently. I don't mind there is a way to intentionally break (even local) coherence ( To me, the problem is simple: if Anyway Haskell2010 does not allow any form of overlap. Also requires global coherence by clearly stating:
|
Would a simpler fix be to have this sort of flexible instance trigger Here the instances for |
We've never had pragmas that only affect warnings before (outside of the ones that just turn them on and off entirely)
Anything along the lines of
i.e. you rely on the author to give the "right" refine declaration for their intended usage. And what if... they don't? |
@tysonzero, that Also, such patch makes dependency of either |
|
Can you break down the examples into small representative pieces? So far the two "(C)-only" examples I have seen were very compatible with this proposal. I have given
I still haven't seen an instance that your proposal accepts that this proposal does not. I'm not saying I doubt there existence entirely, but I would like to see one, and I would like to assess it's practical significance.
I should have clarified that by current Haskell I meant without the broken FlexibleInstances. Yes a lot of transformation are not valid with FlexibleInstances. My bad.
Well with this proposal you would choose what you want when you define
GHC/Haskell today does NOT suffer from this unless you turn on This is also quite important to me and part of why I wrote this proposal. I do not want it to ever be a race between two libraries to define an instance, where the first to define it makes the other instance invalid to define. My proposal (and existing GHC/Haskell without FlexibleInstances) achieves that goal. |
Neither of those really "turn them on and off entirely", they just decide that a certain action (using the specific value) causes a warning. It's pretty much the same idea, although I will agree it's obviously more complex than just "if you use this value then emit a warning". Also
That would absolutely throw a warning, unless you disabled I'm not sure what exactly you think this proposal does? Because throwing a warning when given code like that is pretty much the exact point of this proposal. |
This apparent pain is all for very very good reason. What if at some point in the future If you really want that instance with my proposal you can just add |
The above has an empty refine clause and so instances can be left bare, as per the proposal. |
In such a case you clearly do not have ownership of "at least one" of those type constructors, in fact an empty |
In my reading of the proposal, instances must own a constructor for one of the type variables named in the refine clause of the class. The class has an empty refine clause. This is why the proposal you wrote does not specify the default refine clause for a class as empty, but instead as specifying all type variables: "By default classes and noinstances will have a Refine that specifies all type variables in the class/instance head" |
Right... so you can't define any instances... because you can't own a constructor for one of the type variables named in the refine clause. Just because a condition is impossible to meet, does not mean you can ignore it.
Right, the default for |
So if there are two variables in the clause, one must own for two. If there is one variable in the clause, one must own for one. If there are zero in the clause, by induction, one must own for...? I guess you intend for it to mean something other than the proposal specifies. Here is a further question: in the proposal there is, |
Right, by "induction" one must also own for zero, since you must always own one, that never changes. This is of course impossible. Which is a good thing and exactly what I want, and exactly what the proposal specifies.
No. I mean exactly what the proposal specifies. You must own one type constructor mentioned in the If I say that to call a function you must give me a number greater than 5 and less than 3, you can't just give me 7 and say "well it's impossible for it to be less than 3, so i'm ignoring that", you just can't call that function.
It's an orphan because you do not own So it would be an orphan regardless, you explicitly would need the owner of the class (or |
Ok, this clears up the confusion. The argument seems to run as follows: If one owns the head of a single parameter class, all is good. In a multiparameter class, we could require that one owns the head for at least one parameter, and the others are at least narrowed down. This is the naive warning. For certain special classes, it may be that we want things to be controlled by one specific parameter or collection of them, so refine lets the compiler "pretend" that the class is only about those specific parameters for the purposes of detecting overlap. I think that an alternate approach could be to specify the fundep interaction better, and "secretly" attach a refinement clause to mptcs with fundeps that refines only the variables on the left hand side of fundeps. That way there is no surface syntax at all, but there are perhaps a few warnings people might not like on mptcs without fundeps (but those cases should be rare, and the warnings can be turned off). |
Your proposal can cover any finite examples by "just" writing at most O(n) Let's stop "Who can say the largest number" game since that's not productive at all. (Side note: discussion here went very long, and tracking common basis like alternatives and concrete examples became difficult. Could you update the proposal itself?)
First of all, this discussion already has conclusion: (C) does not comply with current MPTCs without Sorry, that's me who derailed discussion by rushed mind. You replied along with derailed discussion (which is more customizable / intuitive?)
Suppose there are developers of
Comparing both scenario, I would say (C) and the current situation is better, though that's concerning it was minor version up. Same can happen when any unqualified import is used, so you can say instances like Alice defined is as bad as unqualified imports. |
I don't see how that helps, none of these MPTCs ( |
I'm fine with that, but I still would like you to adjust your proposal to acknowledge the fact that for any case with a depth bounded by some K, (B) can do everything (C) can do. So the example in the README (
Well no... she can just use If she cares about the PVP and general correctness/compatibility, and she thinks its reasonable for Lea to depend on If she sort of cares but not THAT much, she can use All of the above seems pretty clearly better than (C) or the status quo. Alice doesn't accidentally violate the PVP or break her users code without warning, but she can easily opt out of this mechanism the second it's too inconvenient. |
|
Sorry forgot that Regardless the FD in The biggest red flag for me as to why an FD-based approach won't be satisfying, is that personally I never use FD's, but yet I still have the need for the proposal, so clearly the FD-based solution will leave me in the exact same spot that I am now. |
Yeah I can probably do so in not too long, just will require a bit of planning so I can cover the various things we discussed. |
What if we solve this and the |
It has, and sorry I've abandoned hope of keeping up. I did see:
In terms of type improvement and interaction with overlapping/orphan instances, there's no difference between FD's vs Type Families (including with I don't like to blow my own trumpet, but the proposal for 'Instance Apartness Guards' (see my earlier comment) needed to go into a great deal of detail. FD's/TF's were tractable only because the guard was explicit on each instance. Note that you might have multiple FD's on a class (or multiple superclass constraints using Type Families -- esp because There's no help from any of the type theory here: the academic papers essentially all assume no overlapping instances; and assume global visibility of all instances. |
How would that interact with I would still personally want the ability to define my own |
I never use |
I would assume we just check at instantiation site in such a case. Note, btw, that this proposal seems to only affect type classes, and as far as I can tell has no relationship to type families, which always have stricter rules regardless. |
How would that work in the following case?
It's important to consider how well it plays with associated type/data families and with FD's, even if it is not directly targeted at them. |
Currently it is possible to cause incoherent instance behavior without
-Worphans
giving any warning and without even enablingOverlaps
/Overlappable
let aloneIncoherent
.This proposal introduces a flexible and general mechanism that prevents this incoherence.
Rendered