-
Notifications
You must be signed in to change notification settings - Fork 207
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
Allow Function
getters to be overriden as actual functions
#4159
Comments
A method overriding a function typed getter is probably sound. Overriding in that other direction is probably a bad idea. If nothing else, it will probably hurt performance. Also probably can't be allowed if there is a setter. Or maybe it can, but that risks breaking (more) internal assumptions in the compilers and runtimes. Or we could allow putting |
I like the idea of adding typedef MyAPI = int Function(int a, int b);
abstract class Base {
MyAPI get m;
}
class BaseA extends Base {
@override
final int m(int a, int b) => a + b; // <--- Changed this
}
class B {
void m(MyAPI api) {
print(api(1, 2));
}
}
void main() {
var b = B();
var a = BaseA();
b.m(a.m);
} |
Yes, basically that. So: class C {
final int x, y;
C(this.x, this.y)
static final int add(C c) => c.x + c.y;
final C swap() => C(y, x);
var C updateWith({int? x, int? y}) => x == null && y == null => this : C(x ?? this.x, y ?? this.y);
} would be mostly equivalent to: class C {
final int x, y;
C(this.x, this.y)
static final int Function(C) add = _$add;
static int _$add(C c) => c.x + c.y;
late final C Function() swap = _$swap;
C _$swap() => C(y, x);
late C Function({int? x, int? y}) updateWith = _$updateWith;
C _$updateWith({int? x, int? y}) => x == null && y == null => this : C(x ?? this.x, y ?? this.y);
} except that I expect the compiler to be able to optimize away the (Not saying this feature is worth its own complexity, just that if we were to do something like this, that's how I would do it.) |
Interesting! The language Dart has always upheld rules that make it an error to mix methods and getters: It is an error to have a declaration of a getter and a declaration of a method with the same name, both in the same class/mixin/enum body (because of the name clash) and across a subtype path ( However, the actual language semantics does allow for those two kinds of declarations to play the roles of each other, both when it's known statically which one it will be, and when the invocation is done dynamically: class A {
int Function(int) get f => (i) => i;
}
class B {
int f(int i) => i;
}
void main() {
// Use as method.
A().f(1); // Invoke the method.
B().f(1); // Invoke the getter, then the returned function object.
(A() as dynamic).f(1); // Invoke the method dynamically.
(B() as dynamic).f(1); // Invoke the getter dynamically, then the function object.
// Use as getter.
A().f; // Tear off the method.
B().f; // Invoke the getter.
(A() as dynamic).f; // Tear off the method dynamically.
(B() as dynamic).f; // Invoke the getter dynamically.
} So we could certainly allow a method declaration and a getter declaration with the same name to coexist (by being declared in the same class body or at two different points in a subtype path). The dynamic semantics could then be to call the most specific member (e.g., if a superclass has a getter and a subclass has a method then we use the method). When the most specific declarations are equally specific (that is, the same class body declares a getter and a method with the given name, and no other declarations in the superclass chain are more specific), we'd call the "nearest" member: Method invocation syntax ( In any case, if we need to call the method and there is no method, then we'd invoke the getter, obtain a function object (or get an error, at compile time or run time, depending on the chosen typing) and invoke that function object with the given arguments. Similarly, if we need to call the getter and there is no getter then we'll tear off the method. As a special case, this allows a getter to override a method, and vice versa. However, Dart never allowed this with that level of generality. The reason is probably run-time performance: It gets really expensive if all method invocations must check at run time whether or not the object has that method, and call the getter if there is no method. Dynamic invocations must do this work today (and throw if there is no method and no getter, and then throw if the getter doesn't return a function object), but statically checked invocations should not have to do it. So let's say that we don't even consider the possibility that the choice between a getter and a method can be done at run time for a statically checked member invocation. We insist that a given member access like In that case we can only consider allowing a getter to be overridden by a method in the sense that it's syntactic sugar for declaring a getter anyway, and then making that getter return a function object corresponding to the pretend-method declaration. This would work exactly as described by @lrhn here. We could consider a different perspective: A method tear-off operation behaves like a getter invocation. We could emphasize the 'getter aspect' of a method declaration by adding the word get: class C {
final int x, y;
C(this.x, this.y)
static int get add(C c) => c.x + c.y;
C get swap() => C(y, x);
C get updateWith({int? x, int? y}) => x == null && y == null => this : C(x ?? this.x, y ?? this.y);
} This yields a "getter declaration" (because of the word The effect of having a method getter declaration with the name This is a very thin layer of syntactic sugar, but if it's helpful then it shouldn't be hard to do. Could we do the opposite as well, that is, supporting a method declaration which is always calling another function, and the body of the declaration would just specify how to obtain that function object? The declaration needs to be a method declaration such that it can be a correct override of one or more other methods or method signatures, but otherwise it works exactly like a getter that returns a function object. This is indeed similar to a forwarding declaration #3444 (thanks for the heads-up, @FMorschel!), but we could consider generalizing it a bit. So let's say that class A {
void foo({int x = 0}) {...}
}
// Regular forwarding
class B1 implements A {
final forwardee = C();
B1(this.forwardee);
void foo ==> forwardee;
}
class C {
void foo({num x = 0.5}) {...}
}
// Forwarding to a dynamically computed function object.
class B2 implements A {
void foo ==> {
void Function({num x}) f;
f = C().foo; // Initializing `f`, which could be a complex job.
return f;
}
} The point is that with a This would again be a very thin layer of syntactic sugar because we could just do the following: class B2 implements A {
void Function({num x}) _fooGetter {
void Function({num x}) f;
f = C().foo; // Initializing `f`, which could be a complex job.
return f;
}
void foo ==> this._fooGetter;
} This will forward an invocation of |
I found dart-lang/sdk#59965 today, I'm unsure how this definition works for this request but I'll mention it here so you have as a reference. |
Oh, yes—but I think those two issues are independent. dart-lang/sdk#59965 is only concerned with the case where the member has the name If we agree that (for performance reasons) a getter can only override a getter and a method can only override a method then I think there's nothing new in the story about |
If you have code like the following:
Say you are creating a package and have not added testing or actually called the last line above (
b.m(a.m)
) anywhere. There is no way for you to know that the call will break.If you have something like this:
The code will also work but the declaration at
BaseA
form
is not the best for writing down.I'd like to ask for function getters to be able to be written as actual functions since they would work the same way.
The text was updated successfully, but these errors were encountered: