Open
Description
If you have code like the following:
typedef MyAPI = int Function(int a, int b);
class A {
int m(int a, int b) => a + b;
}
class B {
void m(MyAPI api) {
print(api(1, 2));
}
}
void main() {
var b = B();
var a = A();
b.m(a.m);
}
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:
typedef MyAPI = int Function(int a, int b);
abstract class Base {
MyAPI get m;
}
class BaseA extends Base {
@override
MyAPI get m => (a, b) => a + b;
}
class B {
void m(MyAPI api) {
print(api(1, 2));
}
}
void main() {
var b = B();
var a = BaseA();
b.m(a.m);
}
The code will also work but the declaration at BaseA
for m
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.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
lrhn commentedon Nov 12, 2024
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.
So it's a one-way feature, turning variables into stable final variables.
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
var
orfinal
in front of a method declaration, which makes it a variable that is initialized to the tear-off of an unnamed method with the given functionality. That is, a shorthand forvar f = _$f; T _$f(args) {…}
.Then we keep getters and functions apart.
FMorschel commentedon Nov 13, 2024
I like the idea of adding
var
orfinal
to the declaration. Do you mean like:lrhn commentedon Nov 13, 2024
Yes, basically that.
If you write
var
,late var
,final
orlate final
before a method declaration (afterstatic
if it's static, not allowed afterabstract
orexternal
), then it's effectively the same as a variable declaration with the same name, whose initializer is a reference to an anonymous function with the given declaration. (Except that you can, somehow, initialize a non-late final instance variable to an instance method, because that'd be neat. Object initialization should be able to perform an instance member tear-off before releasingthis
to anyone. Might break some backend assumptions though.)So:
would be mostly equivalent to:
except that I expect the compiler to be able to optimize away the
late
overhead by initializing the fields immediately when the object has been created.(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.)
FMorschel commentedon Dec 30, 2024
Somewhat related to #3444.
FYI @eernstg.
eernstg commentedon Jan 29, 2025
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 (
extends
,implements
,with
, mixin-on
).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:
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 (
o.f()
) would call the method, and property extraction syntax would call the getter (o.f
).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
o.f()
is statically known to be a method invocation, or statically known to be a getter invocation followed by a function object invocation, and then we can generate good, fast code to do that.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:
This yields a "getter declaration" (because of the word
get
that occurs at the usual position), but it's also a "method declaration" (because of the formal parameter list<...>(...)
or(...)
after the name). So let's call it a method getter declaration.The effect of having a method getter declaration with the name
m
(which can be a private or a public name) would be that a method with a fresh private name_fresh
is added to the class, and a getter with the specified namem
is implicitly induced, returning a tear-off of_fresh
. The formal parameter list and body of_fresh
are taken from the method getter declaration with no changes. The implicitly induced declarations arestatic
if and only if the original method getter declaration isstatic
.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
==> {...}
denotes a dynamically computed forwarding operation: The member forwards the invocation to the function object which is returned by the function body{...}
:The point is that with a
myB1
of typeB1
,myB1.foo
is an indirect way to callmyB1.forwardee.foo
, faithfully preserving the semantics of the invocation. Similarly,myB2.foo
will faithfully forward the call, but the forwardee is computed dynamically as the value returned by the function body{...}
in the construct==> {...}
.This would again be a very thin layer of syntactic sugar because we could just do the following:
This will forward an invocation of
foo
on an instance ofB2
to an invocation of the function object returned from an invocation of the getter_fooGetter
, again faithfully preserving the semantics (including using the actual default values of the forwardee, if needed).FMorschel commentedon Jan 30, 2025
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.
eernstg commentedon Jan 30, 2025
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
call
, and it's only concerned with the implicit mechanism that turns a function invocation of an object into an invocation ofcall
on that object (so the execution ofo()
works likeo.call()
, implicitly). Finally, it's only concerned with dynamic invocations. The implicit invocation of.call
is applicable whencall
is a method, but not when it is a getter, and the issue reports that the run-time error that we should get when it's a getter is not actually thrown.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
.call
. It makes no difference if we can write a somewhat special getter or method using syntax likeC get swap() => C(y, x);
orvoid foo ==> forwardee;
.FMorschel commentedon May 5, 2025
I was thinking a bit more about this. I don't believe that getters are allowed to have type parameters (of their own) currently (right?), so any functions that accept those would not work if this were implemented today. Do we have an issue about that functionality?
eernstg commentedon May 5, 2025
One proposal about generic getters can be found here: #1622, and we don't have them (yet ;-).
It is OK to have a getter that returns a generic function, and this works like a special case of generic getters:
This special case is really special, though; for instance, we wouldn't be able to declare one list of formal type parameters for the getter, and then having that getter return a generic function with a different list of formal type parameters.