-
Notifications
You must be signed in to change notification settings - Fork 200
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
Allow sealed base class
, because it is useful
#3121
Comments
Could we do so for |
We have no modifiers for controlling what is allowed in the current library, because they don't work anyway. Which is precisely what you want, to help you from forgetting to add a Workaround: abstract base class _RealSuperclass { ... }
sealed PublicSuperclass extends _RealSuperclass {}
// all subclasses must be `base`-or-more. Or even abstract base class _Based {}
sealed Superclass implements _Based { ... } (But that is getting a little silly, might as well allow the (I'm much more sympathetic to |
It just seems very arbitrary to me to block this particular combination - I don't think it is a crazy situation or anything. It is in fact the very first sealed class I have ever tried to write and I ran into it. Yes there are workarounds, but I don't really see why they are necessary, given we already have the modifiers. It was my understanding that we chose to exclude the combinations of modifiers that we thought were useless, but in doing so we used the wrong table, and should add back the ones that are in fact useful, when the correct table is used (the one that includes capabilities within the current library). |
Can you elaborate what you mean with "capabilities within the current library"? The reasons Should it have been? (Well, Erik thought so. He didn't manage to convince everybody else.) We did focus mainly on the effect of the modifiers on other libraries because the modifiers doesn't restrict the current library anyway. If you can express all the effects you want on other libraries, you can write a library which satisfies that. (Not saying I'm against allowing |
I think we're in a perfectly good position to allow additional combinations of keywords (following the compositional semantics that we already have). As @lrhn mentioned, I argued already a while ago that the ability to mark a We might want to wait a bit and see how the usage evolves, but if we decide at some point that the introduction of keyword sequences like
This might have been a visual preference, I don't see any technical reason to consider them mutually exclusive. |
It's quite possible that The original argument was that I'd be very surprised to ever see And That is: I can see The argument for allowing these anyway would be orthogonality, so that if an author chooses to write it it, we can say that it works as they (hopefully) think it does, and then recommend that they remove the unnecessary words. |
For reference, I personally have a usecase for I have a code generator which wants the annotated class to be declared as "mixin" because the generated code uses "with". But I also want to enable the class to be marked as sealed. |
Well that isn't entirely true, because I understand we are also considering dropping the contagiousness of |
We made a deliberate choice to not have class modifiers that let you control capabilities within a library. It's entirely reasonable and meaningful to have them, but it means even more modifiers and more combinations and more complexity. Since a library is all "your code" anyway, I don't think modifiers or combinations of modifiers that are only helpful within a library carry their weight. I think of a library as the "unit of maintenance". If your library is so complex that you need to start using language features to enforce invariants within it in order to maintain it, that's a hint that you might want to split it into multiple libraries. (Though I realize For |
Note that my personal use-case for I expect the input of my code-generators to be "mixin" classes. This is necessary because I have some multiple-inheritance scenarios, where "extending" the annotated classes won't be possible. I don't see how I could do that without |
In code, this means I want folks to be able to write: @freezed
sealed mixin class Example implements _$Example {
factory Example.a() = A;
factory Example.b() = B;
void doSomething() => print('Hello');
}
@freezed
sealed mixin class Another implements _$Another {
factory Another.a() = A;
void anotherMethod() {}
} where A/B are code-generated and ends-up being: class A with Example, Another {}
clas B with Example {} which enables: switch (Example.a()) {
case A(): print('first');
case B(): print('second');
}
...
A()
..doSomething()
..anotherMethod(); |
I disagree wholeheartedly with this assertion. Libraries are not written by a single person who maintains them forever. And even if they were, when you come back to your own code years later you won't remember anything about it anyways. The more assumptions/intentions about my code that I can express statically as opposed to in a comment that nobody will read, the better.
I don't think the language should be designed in such a way that it assumes all libraries are small. There are valid reasons to have larger libraries, in particular sealed is a good example, we essentially force large libraries.
I really don't think this is confusing, |
Sure, but there is a cost in language complexity for adding more ways to express those. For example, we currently only have library privacy. We could add class privacy and even instance privacy and that might help intra-library maintenance. But is the complexity or more privacy levels worth it?
We have discussed this at length in #2723 and #2595 (and probably elsewhere). You can reason precisely about the capability modifiers in terms of subtracting capabilities (which is what they actually do in terms of semantics). But the words we chose for them were deliberately chosen to enable users to reason about them in terms the positive capabilities they allow. If we wanted them to think about negative capabilities, then It's definitely not a perfect design but it's the best set of words and semantics that we could come up with after beating on it for a very long time. |
In this case we already have the modifiers, and the existing meaning of the modifier is consistent with what I think it should mean, so the complexity should be minimal, otherwise I would agree. I wouldn't be surprised if it actually made the code less complex because not allowing it probably introduces more implementation complexity? The main complexity would probably be in language tests. In terms of user complexity, I think it is definitely more complex to have certain combinations that are not allowed, and seem arbitrarily banned from a user perspective. There is no real reason as a user to expect this combination of modifiers to be banned so it's a surprising thing you have to learn.
I still don't think |
I'll admit that I read the "positive" modifiers negatively too, reading I still like the positive modifier names, but that's because they do say what you are limited to, rather than saying what you can't. Then you'd have to think what's left. The But it's still a restriction, and if I see two restrictions, I have no problem combining them and applying both. In most cases, I don't even need that, because the most restrictive one comes first. If I see (That's also why I personally have no problem with |
@jakemac53 wrote, responding to @munificent:
Agreed. @lrhn wrote:
and also:
So let's allow all the combinations! 😄 |
I'm not particularly worried about the implementation complexity. I'm much more worried about user confusion when looking at an API. If they're reading the docs for some API and they see a class marked I'd rather have a But this is also not a hill I will die on. If you think this is really useful... I guess it's OK? |
True, but it would then allow having that implementation detail to begin with, which we prevent today. We think can (and should) work on the leaking, even with the features we have today. Showing So if DartDoc doesn't shown anything except If a user looks directly at the declaration, then Or if there is a If you can't extend or implement, you don't need to know whether something is a class or mixin to begin with. So, what if DartDoc shows (plus
That should cover all the available operations in other libraries. Any more information is leaking implementation details. And people reading the code can also stop at the first It's only if you see no modifier or |
In general I think our logic for what the valid combinations are was a bit flawed because we used the table for what is allowed in other libraries, but should have used a table based on what is allowed in any library (including the current one). There may be other valid use cases that are not allowed, other than this one, from within the same library.
Use case
You have a sealed class, and you want to ensure all possible implementations of that class are transitive extensions of it.
You can achieve this today by making all the subtypes in your library be themselves
base
classes, but it could be easy to miss one on accident, in particular when adding new ones. Allowingsealed base
would allow you to enforce this property, sincebase
is transitive even within the same library.Concretely, this is coming up for the
Code
class in macros, I want every piece ofCode
to ultimately extend the base sealed class, but they must go through some more specific subtype to get there (DeclarationCode
,ExpressionCode
, etc).It is OK for people to build their own subtypes of those, but only through extension. We want to be able to put in some form of validation in their constructors, and ensure that those constraints are met.
The text was updated successfully, but these errors were encountered: