-
Notifications
You must be signed in to change notification settings - Fork 214
[Class Modifiers] Adding mixin
confuses positive and negative capabilities again
#2723
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
Comments
I think it's OK as currently defined. The We have a I'd say that you cannot mix-in a non- I think it works, both because of Dart's history and the stand-alone |
This isn't how it is currently specified. As currently specified, And |
An If we allow non- I'd prefer to not allow using a non- The disadvantage of that would be that you can't declare an open class that only you can mix in. If you make it mixin-able at all, it's mixin-able to everybody who can extend the class. The (big, IMO) advantage is that we no longer have to do seemingly random checks on a class to see if it can be used as a mixin, whether it wants to or not. Instead, a The reason we got down to three modifiers is that we only had to cover four cases (can extend × can implement), and we can use "no modifier" for one of them. If we consider Ignoring that "mixinability" is a property of a I think my view here, that I expect the I still do have some problems with the modifiers' meaning wrt. mixins.
The problems come when I try to apply
They should probably do the same for a
So a bare Inside the same library, you can do everything to a class that you can to a plain class, and you can do everything to a mixin that you can to a plain mixin. And you can do everything to a |
@lrhn I don't really understand your class tables. You have a large number of entries for which you are allowed to add For the mixin declaration tables:
|
Adding Outside the library, So, total table that I want, probably not what we currently have:
As for The apparent difference might come from you not usually considering Base gives you (only) the ability to inherit implementation, not give a new implementation as an interface. |
I am adamantly opposed to having to explain these in terms of negative capabilities. It's too confusing. If I see I buy everything in the In the In the |
It sounds like you read "mixin" as a capability. "If it says Mixin is not a capability, it's a declaration. A plain Generally, the |
@lrhn I think I see how you can come up with an internally consistent story using the model you describe, but it's terribly complex. "You see, we have three different things, Stepping back, what is the point of using using a
For the latter, if we think supporting this combination is important, we could simply allow So the only remaining interesting cases are the ones that allow instantiation:
Other than
I still don't love that This is all assuming that we go with your model in which classes may not be mixed in even with their own library. If we do think that the cases that I've cut out of the table above are useful, then it seems to me we'd be much better of just saying that classes can be mixed in internally to a library no matter what, and then we end with most of the
|
That's #1942. I've argued that it might confuse people to the difference between classes and mixins, but at this point, I'd do anything to disallow deriving mixins from arbitrary classes.
I could easily be convinced to not care about that at all. I'm not aware of any class which is used both as a mixin, and to be instantiated. It makes very little sense, since a mixin is something you want to add to something else. It has no generative constructor, so it has almost no interesting state (it can do So just doing #1942, not allowing We could also say that only legacy code can extend mixins, and migration to 3.0 requires changing
I don't think it does. The capability of |
I basically like all these proposals! 😸 But I do tend to prefer having One point I'd like to make is that a list of 23 or 16 different keyword sequences looks more complex than it is. We can demonstrate the underlying orthogonality by introducing an explicit separation of certain aspects of the declared entity. In short, a declaration which is relevant to this discussion has a set of kinds, a set of derivation capabilities, and an instantiation capability:
Being a class implies a number of things (e.g., the ability to have members, possibly with an implementation), and similarly for a mixin. They both support implementation inheritance, but in different forms (subclassing vs. mixin application), but it is possible for a single declaration to support both kinds. Next, we can specify some derivation affordances:
Finally, we can specify the ability to have direct instances:
Note that all these properties except Now we can basically use every combination of these aspects, and the meaning of that combination is determined directly by the meaning of its parts. We get 2 times 4 times 2 combinations using This is exactly @lrhn's table here, except that I understand that we can make the list shorter, like @leafpetersen's last list here, which is 14 elements (plus However, I don't think the 20 element list is so daunting when we take into account that it consists of an orthogonal combination of 3 clearly separate parts. In summary, I think we should keep @lrhn's proposal here on the table. |
Wow, there is a lot of tables here. Let me respond to a couple of high level points and go from there. Positive versus negative capabilitiesOne of the long-standing challenges with this feature has been finding intuitive keywords and I agree strongly that being able to use positive words and reason about them as positive capabilities is the strongest aspect of what we settled on (before Disallowing mixing in non-mixin classes within the same libraryI think we should continue to allow any class declaration to be used as a mixin with the same library. It keeps the proposal more regular: All of these modifiers only affect what other libraries are able to do with the type. I think it's also more consistent with Dart: We say that every class and mixin declaration also gives you an interface for free, so it makes sense that a class also gives you a mixin for free when it makes sense to. Also, pragmatically, I believe allowing you to mixin classes inside the same library makes our life a little simpler in terms of figuring out which set of combinations we need. Being maximally permissive inside the library means we don't need to worry about giving users a way to opt in to capabilities internally independently of exposing the same capability externally. Mixin as a modifier@lrhn's take that Kinds and capabilitiesI really really like @eernst's breakdown where The tablesI went through all of the rows in Leaf's table and Lasse's table and compared it to the most recent set of combinations in my PR. The only differences are that I allow some modifiers on
And I disallow a couple of combinations that Lasse allows:
I believe the differences here are:
If you flip both of those assumptions:
Then I think you end up with the table I propose. So I think we just need to reach agreement on these two points and the specific set of combinations will naturally flow from that. Does that sound right? |
I'm basically fine with your proposal, but I continue to believe that the three additional entries that your proposal has over mine add very little value. Specifically:
So while I'm ok with having these, I think they add relatively minimal value. |
I agree that some combinations are not particularly useful, so the question is whether we go for full orthogonality or not. If we don't, we need to determine which are not useful enough, which of the equivalent forms is the one we'll approve of, and make our tools recognize the rest, and probably suggest what you should change them to. ("You wrote My only goal with introducing (I still don't see the benefit of allowing mixing in a class inside the same library. Just declare it as a mixin. If we allow you to mix in classes inside the same library, we can also slacken the rule about using classes with super-classes as mixins. After all, you can see all mixin applications in the same library, so you can eagerly check that the |
mixin
confuses positive and negative capabilities againmixin
confuses positive and negative capabilities again
My general reasoning for prohibiting a combination is either:
That does leave combinations that aren't particularly useful. But I left those in because... who knows? Maybe someone will come up with a use for them? We tend to be optimistic in terms of making features be as general as possible even if we don't have compelling use cases in hand. I don't know if someone will find a use for That being said, I'm certainly not strongly attached to them, and if you'd rather us be a little more prescriptive and leave these out, I'm fine with that too. I expect most combinations will be very rarely used in practice except for |
I'm fine with this. My main objection was/is to things like |
I'll oppose the interpretation of Again, that only makes sense if you cannot mix in a non- As Erik put it:
A |
Closing this because we've settled on the keywords. |
The most recent proposal for capability modifiers adds a
mixin
keyword which allows using a class as a mixin. Prior to that, we were able to finesse the issue of whetherinterface
andbase
were positive or negative capabilities, since you never had to specify more than one of them. This was convenient, because they are positive sounding keywords, and it is very confusing to have to reason "backwards" from negative capabilities (see discussion in #2595). The proposal in that state had the property that if any capabilities were listed, then all of the available capabilities were listed.Adding
mixin
as currently specified breaks this model, because the specification makesinterface
andbase
negative capabilities, and makesmixin
a positive capability. That means that combininginterface
orbase
withmixin
requires reasoning about "residual" capabilities. That is, one says thatbase class
is class which can be extended, andinterface class
is one which can be used as an interface. But amixin class
is a class which can be extended, implemented, or mixed in. So it no longer lists all of its capabilities. When you add a modifier, you therefore have to start reasoning negatively: addinginterface
takes away the ability to extend. Sointerface mixin class
is a class which cannot be extended (but can be used as a mixin or an interface). This is confusing to start with (switching from positive to negative) but what seems worse is that if I start thinking of these as negative capabilities, then I naturally would expect that since I can have abase mixin class
and aninterface mixin class
, then I should be able to have abase interface mixin class
which can only be mixed in or constructed. This follows naturally: I have amixin class
, I can remove the ability implement by addinginterface
and I can remove the ability to extend by addingbase
, so surely I can add both? The specification currently does not allow this combination syntactically.I see three possible ways out of this.
First, we could choose to make
interface
,mixin
andbase
behave uniformly as monotonic positive capabilities. That is, amixin class
can only be mixed in, and you have to sayinterface mixin class
if you want to be able to use it as an interface. You then need to be able to sayinterface base mixin class
if you want to allow everything, because plainclass
doesn't allow mixing in anymore.Second, we could just choose not to allow
mixin
to be combined with anything else. Note that:interface mixin class
(and similarlyinterface mixin
) is not very useful. Stopping someone from extending something while allowing them to still mix it in (which they can do trivially onObject
) doesn't seem to add anything.base mixin class
(and similarlybase mixin
) could be useful. It's nice to be able to stop someone from using something as an interface.sealed mixin class
(and similarlysealed mixin
) don't seem to make sense to me, since that breaks exhaustiveness.So only one of the combinations is really useful.
Third, we could say that a
mixin
class can be extended and mixed, but not implemented, unless it is marked with `interface.base mixin
is not allowed (there's no point)sealed mixin
is not allowed (likewise)interface mixin
is allowed, and becomes the "non-breaking" migration path: if you want to preserve the behavior from before this language change you need to changeclass
tointerface mixin class
.The third option is appealing to me.
mixin
behave in the most useful way: you must mixin (or extend if you want) and can't implement it.interface mixin class
.The one wrinkle with option 3 that I see is that it would naturally suggest that non-class mixins should also not be allowed to be implemented unless marked with
interface
. This would be another breaking change.cc @munificent @eernstg @lrhn @jakemac53 @natebosch @kallentu @stereotype441 @mit-mit
The text was updated successfully, but these errors were encountered: