Skip to content
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

Open
FMorschel opened this issue Nov 12, 2024 · 3 comments
Open

Allow Function getters to be overriden as actual functions #4159

FMorschel opened this issue Nov 12, 2024 · 3 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@FMorschel
Copy link

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.

@FMorschel FMorschel added the feature Proposed language feature that solves one or more problems label Nov 12, 2024
@lrhn
Copy link
Member

lrhn commented 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 or final 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 for var f = _$f; T _$f(args) {…}.
Then we keep getters and functions apart.

@FMorschel
Copy link
Author

I like the idea of adding var or final to the declaration. Do you mean like:

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);
}

@lrhn
Copy link
Member

lrhn commented Nov 13, 2024

Yes, basically that.
If you write var, late var, final or late final before a method declaration (after static if it's static, not allowed after abstract or external), 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 releasing this to anyone. Might break some backend assumptions though.)

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 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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

2 participants