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

Implicitly create static methods as wrappers for instance methods #3786

Open
Levi-Lesches opened this issue May 8, 2024 · 4 comments
Open
Labels
feature Proposed language feature that solves one or more problems

Comments

@Levi-Lesches
Copy link

Levi-Lesches commented May 8, 2024

In Python, any instance method is really a static method with a self parameter, and the object you call the method on is implicitly passed first to this method. For example:

class User:
  def __init__(self, name): self.name = name 
  def say_hello(self): print(f"Hello, I am {self.name}. How are you?")

alice = User("Alice")
bob = User("Bob")
alice.say_hello()
User.say_hello(alice)

users = [alice, bob]
greetings = map(User.say_hello, users)

In Dart, we often like to use tear-offs instead of passing closures around. A useful example is the following:

class User {
  static String makeGreeting(User user) => "Hello, I am ${user.name}. How are you?";

  final String name;
  User(this.name);
}

void main() {
  final users = [User("Alice"), User("Bob")];
  final greetings = users.map(User.makeGreeting);
}

However, it is no longer possible to pass a tear-off if we make the greeting an instance method:

class User {
  final String name;
  User(this.name);

  String makeGreeting() => "Hello, I am $name. How are you?";
}

void main() {
  final users = [User("Alice"), User("Bob")];
  final greetings = users.map((user) => user.makeGreeting());
}

My feature request is to implicitly (or on-demand) make available a static method that's equivalent to the Python version of every instance method. Not advocating for instance methods to be replaced by static methods. But, if there's an instance method named A.b(), then there should be a static method A.$b(A a), and it should be possible to refer to it as just A.b as syntax sugar. In other words:

class User {
  // implicitly generated
  static String $makeGreeting(User user) => user.makeGreeting();

  final String name;
  User(this.name);

  String makeGreeting() => "Hello, I am $name. How are you?";
}

void main() {
  final users = [User("Alice"), User("Bob")];
  // translated to User.$makeGreeting
  final greetings = users.map(User.makeGreeting);
}

The semantics seem to be well-defined as far as I can tell. The alias of User.makeGreeting for User.$makeGreeting is safe because there cannot be a static member with the same name as an existing instance member, and using an instance member without an instance is always an error. In other words, the alias strictly adds functionality and doesn't break any existing code or create conflicts. Additionally, the compiler may only decide to generate these if they're directly referenced in the first place. This would allow us to use tear-offs way more frequently, especially in JSON contexts where (obj) => obj.toJson() is quite common.

Edit: One specific case has been pointed out to me where this could cause a conflict:

class ClassName {
  void methodName() { }
}

ClassName generateInstance() => ClassName();

void main() {
  final instance = generateInstance();
  final ClassName = instance;
  ClassName.methodName();  // refers to the instance method 
}

In cases like these, where ClassName.methodName is already a valid identifier and would thus cause a conflict, I would say to not generate the implicit static wrapper, to avoid breaking existing code and causing "magic" behavior. But I'm open to whatever makes more sense on a case-by-case basis as well.

@Levi-Lesches Levi-Lesches added the feature Proposed language feature that solves one or more problems label May 8, 2024
@lrhn
Copy link
Member

lrhn commented May 8, 2024

Using simply ClassName.instanceMethodName to get a closure abstracting over a ClassName would work today.

It won't work if we allow static and instance members with the same name in the same class.

Otherwise the idea isn't bad.
Not sure how well it works for methods with parameters, though.

@Levi-Lesches
Copy link
Author

It won't work if we allow static and instance members with the same name in the same class.

Correct, this proposal kind of hinges on that: ClassName.methodName is always a shorthand for the corresponding instance method name and is therefore reserved. It's not breaking currently, but it does restrict future possibilities. I think this is a good use for it, as it would be pretty confusing if ClassName.methodName was too different than instance.methodName

Not sure how well it works for methods with parameters, though.

I left them out intentionally because they certainly do complicate things. In general, you'd probably be safe if you said "let the instance be the first positional parameter, just like Python's self". But I'm indifferent to it, because the main purpose of this proposal is to allow for more convenient tear-offs, and you're unlikely to get a useful tear-off out of this -- unless you specifically have a closure of the form (a, b) => a.method(b),

@Wdestroier
Copy link

@Levi-Lesches This feature request should effectively add the only method reference expression Java has and Dart doesn't yet, right?

image (1)
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

@Levi-Lesches
Copy link
Author

Sounds like 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

3 participants