-
Notifications
You must be signed in to change notification settings - Fork 214
Parametric type parameters? #3620
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
Strawman syntax: A type is static-only if a parametric type variable occurs anywhere in it. A static-only type cannot be used as:
You can use a parametric type parameter in a type alias, an extension or extension type. Or a class. If you need to know whether a type parameter is parametric at the call point, it means instance members and function values can be restricted. A function type With so these restrictions, it's there anything useful left? If we get variance annotations, we can perhaps require parametric class parameters to be covariant or invariant. |
Very nice analysis!
I like that! It lines up well with phrases like "static type" or "static analysis", suggesting something which is only a compile-time phenomenon.
I suspect we'll have to exclude function types: "It is a compile-time error for a function type to have a return type or a formal parameter type which is a static-only type." The reason would be, loosely, that the signature types of a function object are inherently reified. For example, what would we do in the case where a The idea is that it cannot be observed that there is no reification of a static type parameter, because no code which would be able to see the difference is allowed to compile and run, and I don't think we can do that if some functions have static type parameters in their signature.
It should be possible to express all the designs using static type parameters that other programming languages are expressing if they have sound typing and do not reify type parameters. (For Java, for example, we would then restrict ourselves to programs that do not give rise to a warning about an unchecked exception.) That's not nothing... |
If That doesn't mean you can't have a function type Example: R apply<static out R, static in P>(R Function(P) f, P arg) {
R Function(P) function = f; // Valid assignment, no runtime check needed.
return function(arg);
}
int i = apply<int, String>((String s) => s.length, "arglebargle"); This should work. Every parametric type is statically sound, and there are no runtime type checks needed. The problem isn't with parametric types occurring in function types, it's creating a new object with a runtime type where the type occurs, because that's a reified non-parametric occurrence. I guess a parametric type can't be used as an upper bound of another non-parametric type parameter. S foo<static T, S extends T>(T value1, S value2) => (value1 is S) ? value1 : value2; If we invoke this function dynamically, T id<static T>(T value) => value;
(value as dynamic)<int>("a"); and if that Maybe we just have to keep parametric type parameters for a second, while invoking a function dynamically. |
I think a key problem here is that "is never needed" doesn't specify by whom. Consider: class C<T> {
// Nothing...
} It seems like this class could make test<T>(C<T> someC) {
if (someC is C<int>) print('A C of int!');
} I suspect in practice that it would be hard for library authors to know when it's a good idea to opt out of runtime reification of type parameters not just for themselves, but for all users of their API. If you really do want to drop a type argument on the floor, you can kind of do so today like: class C<T> {
factory C() => _ErasedC();
}
class _ErasedC implements C<Never> {} |
Maybe it's safer to only allow parametric type parameters on functions, not classes. If we have A parametric type parameter is really a way of saying that the class itself doesn't know what this type is, just it's the same type everywhere it occurs, and that any values of that type are valid. But you also can't use a parametrization using class Choice<static T> {
final T _true, _false;
const Choice(T ifTrue, T ifFalse)
: _true = ifTrue, _false = ifFalse;
T choose(bool choice) =>
choice ? _true : _false;
} has the type it has, and can be safely upcast, but it doesn't know the type itself, so it can never be downcast. Since the status quo is to do nothing, we may need some use-cases. |
Very interesting discussion - hard to say about the value we'd get for runtimes. Dart2js already has a pass that removes type-parameters if they are not used at runtime. So chances are that if you start using parametric type parameters, we were already optimizing those away. That said, dart2js can be helpful to determine the value of this feature. We have evidence that there are some savings from this, but the evidence changed over time (in fact, @fishythefish was going to analyze whether we should remove this optimization). Also, I am not sure that today's benefit are from cases we can statically enforce. For example, dart2js does remove the If it's valuable to the discussion here, we could look at what are the type-parameters eliminated today by dart2js on some sample apps and see whether they would be iteresting candidates for parametric type parameters. |
I was going to ask if removing the type information that is not being queried in the source code couldn't be a compiler optimization step, or if it would be too slow/complex to analyze for the entire program. However, @sigmundch somewhat answered my question by stating that Dart already does that in some runtimes. As a language user, I'm almost certain that people would favor having these optimizations handled by the compiler rather than burdening users with the addition of a new keyword, which may even impose restrictions on consumers. |
Does that imply generic functions with static type parameters (or non-generic functions that use enclosing static type parameters)
Also the compiler may perform this optimization in more places - so having having this in the language doesn't eliminate the compiler work. |
As a user of ML-derived languages, I'm generally a fan of parametric polymorphism, but I'm also unsure how much actual benefit we'll get from making this a Dart language feature. (Of course, if we make all type parameters parametric, that's another story. 😁) I started drafting a longer reply yesterday, then ended up deleting it when I realized it was basically a +1 to everything Siggi said. Having had some more time to digest this thread, I have some additional thoughts:
A more general concern I have is that this seems like a language feature that asks users to make the effort to opt in, but primarily benefits the compiler. I suspect that such a feature is unlikely to be used extensively unless we can also extol the benefit to users (and more tangibly than free theorems).
I'm not so sure about this. We take reification, type tests, etc. for granted in Dart, but there are very successful languages that deliberately lack these features. IMO, code that requires these features often suffers from poor data representation, similar to code that needs to use I think what makes this a hard decision for library authors is the lack of tangible benefit in exchange for imposing the restriction (in contrast with, say, class modifiers).
Big +1. Are most users just going to perceive this as extra typing for no clear benefit? |
Uh oh!
There was an error while loading. Please reload this page.
We could support a modifier on formal type parameters indicating that it is never needed at run time, that is, no operations are performed where the value of the actual type argument passed to that formal type parameter is needed in order to obtain the correct semantics. For example:
A parametric type parameter does not have to have a representation at run time. This implies that it could be useful from a performance perspective to mark a given type parameter as parametric, if this is possible.
It isn't always possible, of course: It is a compile-time error to use a parametric type parameter in a way that has a run-time semantics. So if
X
is parametric then we can't doe is X
for anye
, ore as List<X>
, we can't evaluate a list literal like<X>[]
, can't doprint(X)
, etc. It is also a compile-time error to use a parametric type parameter as an actual type argument (or as a subterm of an actual type argument) which is passed to a type parameter that isn't parametric.It is more complicated, but it should also be possible to mark a type parameter of a class, mixin, enum, or mixin class as parametric, and maintain the same strict checking that it is indeed not needed at run time.
For dynamic invocations, we would need to eliminate any actual arguments that are passed to parametric type parameters as part of the dynamic invocation semantics itself. I don't know if this is going to be difficult, but it seems reasonable to assume that it is at least possible.
Note that a traditional motivation for having parametric type parameters is that they are associated with the ability to prove certain statements about a program. See Theorems for free for more about this topic. Dart will probably not have any strict and formal provability properties like this, but it is certainly true that the actual type argument cannot influence the behavior of a function invocation if that type argument does not have a run-time representation at all.
If we introduce parametric type parameters then we could also use this to enhance other kinds of checking: We might want to lint every location where a non-transparent extension type is used as an actual type argument for a formal type parameter that isn't parametric.
Another indirect benefit could be that it might be possible to eliminate a larger set of type parameters during compilation if we introduce parametric type parameters: We can safely eliminate all the parametric type parameters during compilation, but this could make it provable that some other formal type parameters can also be eliminated, possibly using a fixed point iteration to get rid of as many type parameters as possible.
@dart-lang/language-team, WDYT?
@mraleph, @mkustermann, @sigmundch, @rakudrama, how important do you think it would be to enhance the support for elimination of type parameters at run time? Does it actually reduce the time/space consumption by large programs in a way that matters?
The text was updated successfully, but these errors were encountered: