-
Notifications
You must be signed in to change notification settings - Fork 205
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
Strict bounds #3795
Comments
Without I'd assume that Are proper-subtype-bounding only valuable for type parameters, or could I have a normal variable with a proper-subtyped type? (Or is that just useless if the excluded type is abstract, and meaningless if not?) Maybe this should only be about sealed types. Or could it be It's a new kind of type bound, which means that the bounded type variable is a new kind of type, with either approach. |
I don't see a problem: import 'unit_with_meter_and_foot.dart';
abstract class MyUnit extends Unit {}
extension on double {
Length<U> toLength<U strictly extends Unit>() => Length<U>(this);
}
void main() {
Length<MyUnit> l1 = 2.5.toLength(), l2 = 4.5.toLength();
Length<Meter> l3 = 5.5.toLength();
var sum = add(l1, l2); // OK, result is `Length<MyUnit>`.
add(l1, l3); // Compile-time error.
} Granted, we might wish to have something which is more flexible and has more expressive power, such that we would be able to make However, a more powerful mechanism might also be a lot more expensive in terms of language and implementation complexity, and we might not want this expressive power so badly that we want to pay that much to get it. ;-)
I think
I think sealed types are the simple case that we don't have to cover, because they can already be handled in a manner which is similar to this proposal. It might still be convenient (and more maintainable) to use this feature with a sealed type, it's just not essential. It is essential with types that are not sealed, though, because we can't enumerate the acceptable types and hence we can't provide for exactly that set of types. This is also the reason why
Would that be useful? It sounds like a restriction which is somehow similar to In any case, it certainly wouldn't allow us to express anything like "every type which is a subtype of
Certainly yes, if |
It could be claimed that we can use existing mechanisms. It is indeed possible to take some steps: sealed class Unit {}
sealed class _GhostUnit {}
abstract class Meter implements Unit, _GhostUnit {}
abstract class Foot implements Unit, _GhostUnit {}
extension type Length<U extends Unit>(double value) {
Length<U> operator +(Length<U> other) => Length(value + other.value);
}
Length<U> add<U extends Unit>(Length<U> l1, Length<U> l2) {
return l1 + l2;
}
void main() {
var l1 = Length<Meter>(1.0);
var l2 = Length<Foot>(3.5);
var l3 = Length<Unit>(-1.7178); // Ouch, no compile-time error!
var l4 = l1 + l1; // OK.
var l5 = add(l1, l1); // OK.
var l6 = add(l1, l2); // Yes! Compile-time error.
var l7 = add<Unit>(l1, l2); // Ouch, we can circumvent it!
} In other words, this is definitely not a complete emulation. |
This is a proposal to enable a new relationship in the declaration of a formal type parameter: It should be possible to declare that the corresponding actual type argument must be a proper subtype of the bound, it is not enough to be equal to the bound.
The type parameter
X
is treated the same way asX extends S
, except that it is an error forX
to be equal toS
(that is,X <: S
must be true, as usual, butS <: X
must be false).This mechanism can play a role which is somewhat similar to that of an abstract class: It is able to provide a certain amount of structure, but it cannot be used directly.
The abstract class describes properties of all subtypes, but we can't create an instance of the abstract class itself. The strictly bounded type parameter will constrain the possible actual type arguments as usual, but the type which is used to specify the bound itself cannot be used. You could say that the strict bound turns the bound into an "abstract type argument".
So how could this be useful?
In general, it allows us to denote a set of types that we often cannot enumerate. In the example above
S
is a sealed class, which means that it is possible to enumerate the immediate subtypes ofS
, but with any type which isn't sealed we cannot know that we have included them all. Also, it might be convenient to usestrictly extends
even with a sealed type, because it is more maintainable.A strictly bounded type parameter could be used with bound
Object?
to ensure that a type argument can be any type except a top type, which is something that we can't otherwise express. In particular, this means that the actual type argument cannot bedynamic
, but it can be another nullable type (and it can even beNull
). This is not possible if we just specify the bound to beObject
(which is currently the quasi-standard way to express that a type argument can't bedynamic
).It can also be used with
Object?
in order to ensure that it is a compile-time error to rely on the greatest closure (that is, the default approach taken when type inference has no information).A strict bound could be used with
Object
in order to avoid a subtle type likeFutureOr<Object>
(see dart-lang/sdk#54311 for some background info about why we might want to avoid such types).A strict bound could be used with a sealed type in order to specify a finite, statically known set of possible type arguments to a generic entity, and at the same time preventing the sealed type itself from abstracting over this set of types as a whole (is the "abstract type argument" usage: all the subtypes are "concrete as type arguments" and can be used as actual type arguments, but the sealed type itself is "abstract as a type argument" and cannot be used directly):
Without
strictly
we cannot ensure that the units are respected:The text was updated successfully, but these errors were encountered: