-
Notifications
You must be signed in to change notification settings - Fork 209
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
Support with and implements keyword in generic constraint, allow multiple constraints for one generic type. #1152
Comments
Allowing multiple bounds on a type variable can easily introduce intersection types. foo<T extends Foo & Bar>(T v) {
var z = v.baz; // What is type of `z`?
return z;
} what would the type of abstract class Foo {
Foo get baz;
}
abstract class Bar {
Bar get baz;
}
class Baz implements Foo, Bar {
Qux get baz => ...;
}
class Qux implements Foo, Bar {
Baz get baz => ...;
} The only correct choice is the intersection type We could say that you can't invoke methods on You can do cheap up-casts to both Using the |
I am not suggesting that mixin A {}
mixin B {}
mixin C {}
class Abc<T with [A, B, C]> {
}
class Test1 with A {}
Abc<Test1>(); // Error
class Test2 with A, B {}
Abc<Test2>(); // Error
class Test3 with A, B, C {}
Abc<Test3>(); // Valid C# Example: class A {
}
interface B {
}
class Abc<T> where T : A, B {
} |
@lrhn mentioned the implied intersection types, but the proposal seems to imply a number of other requirements: If we specify I think we'd need a strong use case and in general some more motivation for why we'd need these new and more detailed constraints on the possible values of type variables. One difficulty is that the ability to constrain the value of a type variable to be a type that Those dependencies would force properties to be maintained that may otherwise be considered implementation details: If I know that Another issue is having multiple edges on the path that establishes a typing relation. If we require that In summary, I do recognize that the ability to use bounds like |
The use case is for more complex type constraints which can definitely happen.
Here is my original setup with this problem // Serializable class. Other classes can extend and implement `serialize`
// to denote that they can be serialized to bytes.
abstract class Serializable {
Uint8List serialize();
}
// Contains a generic type T and wraps it's serialization and comparison methods.
// Since this class is serializable, T must also extend serializable.
// Since this class is comparable, T must also implement comparable
class SerializableContainer<T
extends Serializable
implements Comparable<T>>
extends Serializable
implements Comparable<SerializableContainer<T>> {
// Value that this container stores
T value;
SerializableContainer(this.value);
// Wrap around T's serialize method
Uint8List serialize() => value.serialize();
// Wrap around T's compareTo method
int compareTo(SVal other) => value.compareTo(other.value);
}
// Simple class that wraps an int value that can be serialized and compared
class SVal extends Serializable implements Comparable<SVal> {
// Integer that the wrapper stores
int value;
SVal(this.value);
// A function that serializes the integer
Uint8List serialize() { ... }
// Compare this to another SVal using the integer comparison method.
int compareTo(SVal other) => value.compareTo(other.value);
} The only workaround I know is to make // This time, Serializable implements Comparable.
abstract class Serializable<T> implements Comparable<T> {
void serialize();
}
// Since Serializable implements Comparable, we do not need to add that constraint here.
class SerializableContainer<T extends Serializable<T>> extends Serializable<SerializableContainer<T>> {
T value;
SerializableContainer(this.value);
void serialize() {}
int compareTo(SerializableContainer<T> other) {
// T extends Serializable which implements Comparable so this call is valid.
return value.compareTo(other.value);
}
}
// Since we need to pass SVal to Comparable, we need to first pass it through Serializable.
class SVal extends Serializable<SVal> {
int value;
SVal(this.value);
void serialize() {}
int compareTo(SVal other) {
return value.compareTo(other.value);
}
} @eernstg I really cannot understand most of what you are talking about. Can you give some code examples and simplify what you're saying? To be honest, I just see a bunch of different letters and I don't think this proposal is that complicated and many other languages (Such as C# that I have mentioned above) have implemented this already. |
C# solves the problem of intersection types by allowing interfaces to get implemented explicitly. When type bounds overlap, the object needs to be casted. Rust does it a similar way. Traits can be implemented independent of each other. Then, when trait bounds overlap, the caller needs to explicitly call the trait method on the object (for the user, it does the same thing as C#). (By the way, being able to implement interfaces by means of virtual extension methods would also be a really, really neat feature). Dart already does something very similar with extension methods: when multiple extensions implement the same method, users have to call one extension explicitly. So, while it might not be a simple feature to implement, it is very, very useful. For example, I encountered the following problem: I needed the type bound |
Long ago, @nathanfranke wrote:
Ah, sorry, I overlooked that comment! Just noticed it now that there's a new comment on this issue. But let me give a couple of comments on the initial text of this issue:
This is the part that I was responding to. An example was given: // A type parameter with a bound, as it is written today.
class Abc1<T extends Comparable<T>> { ... }
// Proposed new feature.
class Abc2<T implements Comparable<T>> { ... } I interpreted this proposal to mean that we would be able to specify constraints on type arguments in new ways, with a new meaning (I assumed that it wouldn't merely be two alternative syntaxes for exactly the same thing). So, presumably we'd have the following: class A1 extends Comparable<A1> {...} // Assume that this class has no errors.
class A2 implements Comparable<A2> {...} // Ditto, no errors.
void foo(
Abc1<A1> x1, // OK.
Abc2<A2> x2, // OK.
Abc1<A2> x3, // Compile-time error! Has `implements`, but `extends` is required.
Abc2<A1> x4, // Compile-time error! Has `extends`, but `implements` is required.
) {} The rule would now be that an This is the kind of ruleset that I thought would require a really convincing use case. I honestly don't see the problem that it solves, and I think it would cause a large amount of complexity. Also, I'd consider this kind of rule to be a pervasive violation of encapsulation: The issue mentions a different topic (that I consider unrelated, or at least very different):
This is basically a request for intersection types, at least when they occur as upper bounds of type parameters. We could certainly introduce intersection types into the Dart type system, but this is a rather substantial addition, and there would be many details to sort out. In any case, that would be a separate proposal, with details. It could be made part of a union types proposal (cf. #83), because union types give rise to intersection types, and vice versa. So it would probably be a good idea to discuss intersection types in #83, or at least to add a comment there such that the #83 crowd is aware of the proposal about intersection types. |
My 2 cents on the matter @eernstg :
As mentioned by @lrhn right in the first comment,
|
To be perfectly clear: Dart uses There was no discussion about whether another word might be more or less precise, it had to be much better to compete with something people already knew. It existed, it worked, people already knew it. We have no current plans to change the word. That's simply not worth the effort. It still works. There are no glaring problems with it. Changing it would be an incredibly large and wasteful enterprise with very minuscule benefit, if any. If we ever completely revamp type parameter syntax, like adding multiple bounds, or lower bounds, or something we haven't even thought of yet, we might consider whether we should change |
From a Java tutorial on upper bounded wildcards: |
I would like to discuss this topic a bit further. The following: This, just like the previous Edit: I see there is another issue for that: #2709 |
Any updated on this? This would make the language way more structured. In big projects |
No update. No plans to do anything, so might as well close this issue. For multiple bounds, see #2709. |
That's a long title, so I will try to demonstrate this problem.
extends
is only keyword allowed in generic constraints.While classes, mixins, and interfaces are all pretty similar ideas, the fact that generic constraints rely on only
extends
is inconsistent with class signatures and also misleading.For example:
We say
extends
rather thanimplements
. I would prefer this:While classes can only have one inherited class, they can have that and also multiple mixins and or interfaces.
I would like to see the generic constraints follow the same syntax as the class signature. For example:
Edit: Multiple mixins and interfaces are delimited with a comma, and
class Abc<T implements InterfaceA, InterfaceB>
is ambiguous.I propose one of these:
The text was updated successfully, but these errors were encountered: