You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
"Type patterns" were considered as part of the patterns feature, but didn't make it.
Proposal
A "type pattern" is a type where a type argument is not always a plain type. It can also be a binding declaration, or a constrained binding declaration, which captures the type of the actual type argument when used to match against a type or an object. (See: "Existential open".)
Strawman syntax is that <X> or <X extends T> occurring where a type argument is expected will be a binding type pattern.
Example: if (value case Map<<K>, <V>>) { ... use<K, V>... }.
(Another possible syntax is final X/final X extends T, making it value case Map<final K, final V>: .... It just needs to be distinguishable from a type and include a type variable and bound, and the <X extends T> is how we usually introduce a type variable.)
A type pattern of <X> matches any type, and is irrefutable.
A type pattern of <X extends B> matches any subtype of B, just like the non-binding type pattern (aka. type) B.
(That is case Foo<Bar>() and case Foo<<X extends Bar>>(): matches the same types and objects, one just binds the type argument to a variable, just like int() and int x; matches the same objects, one just binds that value to a variable.)
Type patterns can be used in patterns, both refutable and declaration patterns. In the latter, they must be irrefutable (so no bounds that aren't guaranteed to be satisfied by the matched value type, which basically means no useful bounds).
Type patterns do require a feature that Dart doesn't have today: "|Existential Open", the ability to go from an instantiated generic type (including the runtime type of an object) to a type variable bound to its type argument.
This is a useful feature, but it requires actually retaining the type information at runtime. So does any reference to the type variable inside the class itself, so it's not new, it's just possible to add uses on the side, so a class can't convince itself that it never uses its type arguments for anything at runtime, and expect them to be tree-shaken. Someone else might extract the type argument.
Type patterns can only be used to extract type arguments.
It's deliberately not allowed to use a type pattern for an entire value, like switch (value) { case <T> v: ... } which should be matching against the runtime type of value. This is disallowed precisely so that the newly introduced type variables can never be bound to runtime decided type that wasn't passed as a type argument in at least one place in the program.
It also means it shouldn't leak implementation details. The runtime type of a native <int>[] is _GrowableList<int>. Since a type pattern has to be List<<T>>, you can only ask for type parameters of interfaces that you can name. It's restricted to the public interfaces of the API, private types are not leaked unless they are visible in the interface.
Potentially, we could allow destructuring function types, like case <R> Function(<P>): .... That may cause more types to be accessible at runtime and be bad for tree-shaking. It would be useful. (But structural types are not trivial, allowing (<T1>, <T2>) to match a record is the same as matching a value's runtime type. Functions might be different, because it's not the value's type. Or at least it's not any value.)
Other uses
If Dart has type patterns, they could be used in some places where you'd currently have to use a type variable, in some places where you can't use a type variable, and maybe just make some things a little shorter.
Having a List<<T>> pattern is basically the same as having a List<T> pattern with a <T> declaration on the side,
like what we do for void foo<T>(List<T> arg) { ... } /*...*/ foo(arg);, except that type arguments are statically resolved, and type patterns are runtime destructuring/opening the runtime type.
We could allow type patterns in extension declaration's on type:
extensionFooListonList<<XextendsFoo>> {
// X is in scope.
}
extensionKeyMap<K> onMap<K, <V>> {
// K and V in scope.
}
It could also be used in catch clauses, on GenericError<<X>>, but errors are rarely generic.
The text was updated successfully, but these errors were encountered:
lrhn
added
feature
Proposed language feature that solves one or more problems
patterns
Issues related to pattern matching.
labels
Dec 22, 2024
"Type patterns" were considered as part of the patterns feature, but didn't make it.
Proposal
A "type pattern" is a type where a type argument is not always a plain type. It can also be a binding declaration, or a constrained binding declaration, which captures the type of the actual type argument when used to match against a type or an object. (See: "Existential open".)
Strawman syntax is that
<X>
or<X extends T>
occurring where a type argument is expected will be a binding type pattern.Example:
if (value case Map<<K>, <V>>) { ... use<K, V>... }
.(Another possible syntax is
final X
/final X extends T
, making itvalue case Map<final K, final V>: ...
. It just needs to be distinguishable from a type and include a type variable and bound, and the<X extends T>
is how we usually introduce a type variable.)A type pattern of
<X>
matches any type, and is irrefutable.A type pattern of
<X extends B>
matches any subtype ofB
, just like the non-binding type pattern (aka. type)B
.(That is
case Foo<Bar>()
andcase Foo<<X extends Bar>>():
matches the same types and objects, one just binds the type argument to a variable, just likeint()
andint x;
matches the same objects, one just binds that value to a variable.)Type patterns can be used in patterns, both refutable and declaration patterns. In the latter, they must be irrefutable (so no bounds that aren't guaranteed to be satisfied by the matched value type, which basically means no useful bounds).
Type patterns do require a feature that Dart doesn't have today: "|Existential Open", the ability to go from an instantiated generic type (including the runtime type of an object) to a type variable bound to its type argument.
This is a useful feature, but it requires actually retaining the type information at runtime. So does any reference to the type variable inside the class itself, so it's not new, it's just possible to add uses on the side, so a class can't convince itself that it never uses its type arguments for anything at runtime, and expect them to be tree-shaken. Someone else might extract the type argument.
Type patterns can only be used to extract type arguments.
It's deliberately not allowed to use a type pattern for an entire value, like
switch (value) { case <T> v: ... }
which should be matching against the runtime type ofvalue
. This is disallowed precisely so that the newly introduced type variables can never be bound to runtime decided type that wasn't passed as a type argument in at least one place in the program.It also means it shouldn't leak implementation details. The runtime type of a native
<int>[]
is_GrowableList<int>
. Since a type pattern has to beList<<T>>
, you can only ask for type parameters of interfaces that you can name. It's restricted to the public interfaces of the API, private types are not leaked unless they are visible in the interface.Potentially, we could allow destructuring function types, like
case <R> Function(<P>): ...
. That may cause more types to be accessible at runtime and be bad for tree-shaking. It would be useful. (But structural types are not trivial, allowing(<T1>, <T2>)
to match a record is the same as matching a value's runtime type. Functions might be different, because it's not the value's type. Or at least it's not any value.)Other uses
If Dart has type patterns, they could be used in some places where you'd currently have to use a type variable, in some places where you can't use a type variable, and maybe just make some things a little shorter.
Having a
List<<T>>
pattern is basically the same as having aList<T>
pattern with a<T>
declaration on the side,like what we do for
void foo<T>(List<T> arg) { ... } /*...*/ foo(arg);
, except that type arguments are statically resolved, and type patterns are runtime destructuring/opening the runtime type.We could allow type patterns in
extension
declaration'son
type:It could also be used in
catch
clauses,on GenericError<<X>>
, but errors are rarely generic.The text was updated successfully, but these errors were encountered: