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

Extension methods #1122

Closed
wants to merge 8 commits into from
Closed

Extension methods #1122

wants to merge 8 commits into from

Conversation

zygoloid
Copy link
Contributor

@zygoloid zygoloid commented Mar 7, 2022

Explicitly say that we do not restrict which function declarations can have a me parameter, and that there is likewise no restriction on what type that parameter has. This enables a form of extension methods:

fn Negate[me: i32]() -> i32 { return  -me; }
var minus_five: i32 = 5.(Negate)();

@zygoloid zygoloid added the proposal A proposal label Mar 7, 2022
@zygoloid zygoloid requested a review from a team March 7, 2022 22:20
@zygoloid zygoloid changed the title Extension methods Proposal: extension methods Mar 7, 2022
@zygoloid zygoloid marked this pull request as ready for review March 7, 2022 23:51
@zygoloid zygoloid requested a review from a team as a code owner March 7, 2022 23:51
@github-actions github-actions bot added the proposal rfc Proposal with request-for-comment sent out label Mar 7, 2022
@zygoloid
Copy link
Contributor Author

zygoloid commented Mar 7, 2022

Note, the terminology used in this proposal assumes #1119 is applied.

@zygoloid zygoloid changed the title Proposal: extension methods Extension methods Mar 7, 2022
docs/design/expressions/member_access.md Outdated Show resolved Hide resolved
docs/design/expressions/member_access.md Outdated Show resolved Hide resolved
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I'm hesitant on this proposal... I get the impression it builds on qualified function calls, but to me "extension methods" means a significantly different thing in other languages. What's proposed here seems to miss the ergonomic benefits of the feature elsewhere.

It may make sense to provide, but I'd tend to wonder if the divergence from C++ is justified, given call syntax isn't on par with the equivalent in other languages.

docs/design/classes.md Show resolved Hide resolved
docs/design/classes.md Outdated Show resolved Hide resolved
docs/design/classes.md Show resolved Hide resolved
proposals/p1122.md Show resolved Hide resolved
proposals/p1122.md Outdated Show resolved Hide resolved
proposals/p1122.md Show resolved Hide resolved
Comment on lines +130 to +131
- Improves ergonomics by allowing local, ad-hoc introduction of extension
methods without the syntactic overhead of an adaptor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unconvinced by the ergonomic argument.

This is noted by the example at https://en.wikipedia.org/wiki/Extension_method#Current_C#_solutions, where they compare string y = Utility.Reverse(x); with the more desirable string y = x.Reverse();. It seems like this proposal would provide string y = x.(Utility.Reverse)();, but the key advantage noted in the article is eliding the Utility container class (and also, unless qualified function calls turn out to be common, I think the extra parens will be awkward in practice).

Put differently, I feel like the main advantage of extension methods in other languages would be that they're seamless. However, the whole argument against that here is that you want to avoid new name lookup rules. In which case, I don't think extension methods are a reasonable comparison (and you might also want to consider the alternative: provide extension methods similar to how they exist elsewhere).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An ergonomic disadvantage is also that, assuming I'm understanding correctly that this only moves the location of Utility.Reverse in the call, I think there'll be limited adoption. i.e., it's not so unambiguous an advantage that developers will flock to it (I don't understand why I would use this, but I assume other developers would think the opposite), and there'll just be multiple ways of offering member-method-like functions for classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get pretty close to the C# syntax, but it requires an explicit opt-in:

alias Reverse = Utility.Reverse;
// ...
var y: String = x.(Reverse)();

And yeah, I also don't expect developers to flock to this. But we need some rule in this space, and the only question seems to be how much of a barrier and what amount of language complication we want to put in front of people -- even the most restrictive of the alternatives below still permits writing extension methods, with more cumbersome syntax. The simple, orthogonal thing to do is to not restrict which functions can have a me.

There is one way in which this isn't just moving the location of Utility.Reverse in the call: only me has the special addr me behavior that implicitly takes the address of the value before the ..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One ergonomic advantage of method call syntax is that, if the codebase uses a natural language like English that has SVO word order, the method can sometimes be named so that the call syntax matches that order, which can make the code clearer. For example, I find x.Contains(y) substantially clearer than Contains(x, y), because it matches the order of the English phrase "x contains y". That not only reduces my mental effort in parsing the code, it makes me more confident in the meaning of the code: x.Contains(y) is very unlikely to mean "y contains x", whereas Contains(x, y) plausibly could.

Extension methods make that option available even when the "subject" and "verb" are defined by different libraries. However, that may be undermined by the need to qualify the method name in most cases: x.(Utility.Contains)(y) doesn't read very naturally, although it's probably still less ambiguous about the roles of x and y. And of course this advantage doesn't apply at all if the codebase's natural language doesn't use SVO order.

Copy link
Contributor

@jonmeow jonmeow Mar 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, I'm not objecting to extension methods (as a concept), I'm objecting that the syntactic overhead undermines the ergonomic benefit of this particular approach. Some of the question may be from the intuition: is compound member access going to be something that's really common in Carbon, or something that developers rarely see? My assumption is the latter, but maybe others differ.

If compound member access is common, then it reduces the cognitive costs of the feature by making it something people are accustomed to; if rare, then developers will be learning the feature when they see it (and perhaps forgetting it quickly due to disuse), and leading to slower understanding of code and likely more bugs.

Contains(x, y) may not read as great, but it's going to be familiar to C++ developers as a function call, even versus x.(Contains)(y).

In other words -- maybe similar things would exist regardless, as noted in the alternatives, and determined developers would surely find them. But pushing developers towards a smaller set of common approaches has readability benefits too; I'd argue it's lower cognitive load for developers.

proposals/p1122.md Show resolved Hide resolved
proposals/p1122.md Show resolved Hide resolved
zygoloid and others added 2 commits March 8, 2022 17:47
Co-authored-by: Jon Meow <jperkins@google.com>
@josh11b
Copy link
Contributor

josh11b commented Mar 31, 2022

This seems a bit in conflict with the current plan for conditional methods.

See discussion on 2022-03-31

@zygoloid
Copy link
Contributor Author

zygoloid commented Apr 8, 2022

This seems a bit in conflict with the current plan for conditional methods.

Can you say more about how these conflict? I think that conditional methods are a special case of the behavior proposed here, and if that's not the case then that is indeed a concern.

@josh11b
Copy link
Contributor

josh11b commented Apr 8, 2022

This seems a bit in conflict with the current plan for conditional methods.

Can you say more about how these conflict? I think that conditional methods are a special case of the behavior proposed here, and if that's not the case then that is indeed a concern.

The relevant parts of the discussion was:

// Parameter approach, currently documented
class Vector(T:! Type) {
  // `Vector(T)` has a `Sort()` method if `T` is `Comparable`.
  fn Sort[C:! Comparable, addr me: Vector(C)*]();
}

// ...

fn Call(v: Vector(i32)) {
  // Vector(i8*) has a generic Sort function that takes Vector(C),
  // including Vector(i32).
  v.(Vector(i8*).Sort)();
}

(Assumption here is that i32 is comparable but i8* is not.)

The intent of conditional methods was: "Vector(T) has a Sort() method if T is Comparable." However, with this proposal, the interpretation is "Vector(T) has a generic Sort() method that takes a Vector(C) as long as C is Comparable." This affects the validity of the expression Vector(i8*).Sort. I guess my point is that this proposal gives a different meaning to the syntax used currently by conditional methods, replacing it with something that addresses the same use case but with different properties. In addition to making some unexpected code legal, it potentially interferes with defining methods with the same name but apply to disjoint sets of me types:

class Vector(T:! Type) {
  fn Flip[addr me: Vector(bool)*]();
  fn Flip[addr me: Vector(Table)*]();
}

This is not necessarily a blocker for this proposal, but it was definitely a surprising consequence to me.

@zygoloid zygoloid closed this Jun 28, 2022
@github-actions github-actions bot added proposal deferred Decision made, proposal deferred and removed proposal rfc Proposal with request-for-comment sent out labels Jul 25, 2022
Copy link
Contributor

@chandlerc chandlerc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(discovered an old pending comment I forgot to post, sorry...)

**Note:** It's also possible to alias an extension method into a class and call
it using simple member access notation.

The type of `me` is not required to be a class type:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i32 is going to be a class type though....

Maybe "There are no restrictions on the types that can be used with me parameters."?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal deferred Decision made, proposal deferred proposal A proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants