-
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
Function internal parameter type override. #1311
Comments
Cool! It is really nice that this allows for better forwarding. One thing, though: Is |
I actually does not allow for better forwarding. 😡 You could probably also do something with int findSomething(List<int> things, [int min as num = double.infinity]) {
for (var v in things) if (things > min) return v;
return somethingElse;
} or any other wider superclass where you can meaningfully add a default from another subclass of the chose superclass. void foo<E extends Object?>({E value as Object? = const _Sentinel()}) {
if (const _Sentinel() == value) {
... no argument ...
} else {
value as E;
... with argument ...
}
} Here we are adding a sentinel which works even if |
An argument against I'd probably be positive towards I agree that if you're going to use a sentinel value, it would be cleaner to let the system do it for you, without having to introduce a sentinel value yourself. |
I'd probably not make the initialization check trigger initialization. Avoiding that initialization is exactly why I sometimes avoid So having I think simply omitting eager initialization optimizations in cases where the author seems to care about the initialization is going to be fine. The analyzer can warn if a |
No semantic paradoxes needed. The meaning of The So, if you declare: late {hasBuffer} buffer = StringBuffer(something); then that introduces two variables into the scope: Reading All of these operations are orthogonal. They work the same whether the late variable has an initializer or not. Reading an uninitialized late variable with no initializer throws, as if it had an initializer of So, I totally can avoid the word "initialized", I just say "has a value" instead. "Initialization" is just a word we use for the first time something gets a value. I'm arguing against a function named |
I remember discussing this general idea on the white board in AAR a long time ago as part of the discussion of |
I wouldn't let this subsume So, with covariant: class C {
void foo(covariant num x) {}
void bar(num x) {}
void baz(int x) {}
}
class D extends C {
void foo(int x) {}
void bar(num x as int) {}
void baz(int x as num) {}
} the static types of If we use a wider type to allow a different default value, then we don't want the caller to pass something of that type. If we use a narrower type just because we know we're only going to be called with that type, even though a wider type is required, it's because we know something the type system doesn't, so statically being the narrower type would break what we're trying to achieve. |
Several.
Basically, it tries to distinguish the local variable introduced by a function parameter from the function parameter itself. |
The You (and the runtime system) can tell which one it is, and passing an It's just ... we have a way to represent an optional value already, it's nullability. So what if passing |
The tricky part here is that So, basically, the parameter is treated like a variable which is potentially assigned, where normal parameters are always considered definitely assigned. That's actually a reasonable approach to optional parameters, you just need some way to use them, otherwise they are completely inaccessible. If we could ask a potentially assigned variable whether it's been assigned, then we could do the same here. (We currently can't, and I don't really want it, because we'd just be introducing more boolean flags that the compiled code has to keep updated, and that the compiler might not be able to optimize away). The way you allow this potentially assigned variable to be used is in the argument position of an optional parameter. So, I see your idea, and I'll raise you with:
Then you can write: void foo({int foo, int bar}) => bar(foo: if (??foo) foo, bar: if (??bar) bar); One change is that you can now omit non-trailing positional arguments. (We definitely do not want to move the following arguments up in the argument list, that'd be impossible to type). Not sure I want this, but it's a coherent approach to detecting unassignedness. (It's introducing an Also
I totally get it. When And that's annoying, but very much limited to, well, And I still think a viable approach is to have This proposal, for allowing the parameter to have a wider type internally, includes a way to detect omitted arguments, by widening the type internally, and having a default value separate from any user-provided argument, and it has no way to optionally not pass an argument inside an argument list, so it fails my requirement above. Too bad, because it has other advantages :( (About nulllable-meaning-optional:
Working on it, in my sparse spare time 😁 ) |
So Since you write It's still "another My idea of using "definitely assigned" instead of a value requires new operators, but not a new value and type (which is possibly not a a subtype of |
I don't think that is a showstopper as much as it's an "You have to document this behavior now, so users know not to shoot themselves in the foot". The behavior is fine, users of that function should just know that omitting early arguments will stop computation at that point. Completely well-defined and predictable semantics, it's just one extra edge case that you couldn't hit before. |
This discussion sounds like it's trying to solve the undefined/null conflict, for which there are several issues already opened. One simple solution was the combination of allowing non-constant defaults for parameters, and allowing the keyword |
I didn't mean to propose a whole solution in a small comment, I was just pointing to the other discussions on null/undefined and that this issue doesn't really seem to be about that (specifically, the top comment mentions null/undefined and shrugs it off by saying "choose a sentinel value" and that other solutions should be fleshed out in another issue). |
@Levi-Lesches wrote:
We might need something slightly more powerful than a single I'm not so happy about the "null means not passed" idea, because the type of a parameter could admit the value null as a properly passed value, and it could still have a different default value. So we'd need funny exceptions about the case where the parameter type is nullable, or the case where it is nullable and there is an explicit default value, or the case where it is nullable and there is an explicit default value different from null, or all of the above. |
In short, a parameter can have an added
as type
which changes the type of the local variable introduce by a parameter internally in the function, but doesn't change the declared type of the parameter in the function parameter.Example:
The
as type
goes after the identifier, or after the end)
for function-style parameters, and before default values for optional parameters.The
as type
type should be related to the declared (or inferred) type, either a subtype or a supertype.If the
as type
type is a subtype of the declared type, it adds an run-time down-cast of the argument to theas type
type on function entry (in declaration order if there is more than oneas
for the same function), just as for covariant functions. The parameter's local variable's declared type is theas type
type inside the body of the function.This can be used as a solution to #477. Example:
(Because we don't have generic constructors yet, the type the
key
function must beK Function(Object)
, even if you know the iterable's element type).If the
as type
type is a supertype of the declared type, then there is an implicit up-cast (meaning nothing) and the parameter variable simply has theas type
type as its declared type.In both situations, an optional parameter can have any default value which is a subtype of the
as type
type, not the declared type.That allows a function to have an optional parameter which is not
null
, but which can be omitted, even when it's not possible to provide a valid default value for the parameter type. Example:won't leak in the externally visible type that
null
is used internally to represent an absent parameter, and won't allowfoo(x: null)
.Allowing default values outside of the declared parameter type makes it detectable whether the parameter was passed or not. It's not possible to directly pass the default value, so the presence of the default value implies that the original function invocation omitted the argument for that optional parameter.
Being able to distinguish whether an argument was passed or not has historically caused problems for forwarding functions.
Consider a function
foo({int x, int y})
, and a functionbar({int x, int y})
which wants to forward its invocation tofoo
.Currently
foo
would need to have default values for each parameter, thenbar
could use the same default values and callfoo(x: x, y: y)
directly.If
foo
could choose a default value that cannot be passed, saynull
, thenbar
would need to be written as:This affects our own generated "noSuchMethod-forwarders", mixin application constructor forwarders, and other forwarding stubs, and it affects user code needing to forward parameters.
We might want to consider adding a way to conditionally pass an argument, say
=> foo(x: if (x != null) x, y: if (y != null) y)
, but that probably needs more design work if it also has to apply to positional arguments.The text was updated successfully, but these errors were encountered: