Description
UPDATE: Feature specification is available here:
https://github.com/dart-lang/language/blob/master/accepted/2.7/static-extension-methods/feature-specification.md
Original in-line proposal:
Possible solution strategy for #40. This issue doesn't (currently) have an concrete proposal.
Scoped static extension methods are successfully used in C# and Kotlin to add extra functionality to existing classes.
The idea is that a declaration introduces a static extension method in a scope. The extension method is declared with a name (and signature) and on a type, and any member access with that name (and signature) on something with a matching static type, will call the extension method.
Example (C# syntax):
class Container { // Because all methods in C# must be inside a class.
public static String double(this String input) // `this` marks it as an extension method on String
{
return input + input;
}
}
...
String x = "42";
String y = x.double(); // "4242"
The method is a static method, all extension methods do is to provide a more practical way to invoke the method.
In Kotlin, the syntax is:
fun String String.double() {
return this + this;
}
The String.
in front of the name marks this as an extension method on String
, and the body can access the receiver as this
. Apart from these syntactic differences, the behavior is the same.
It's possible to declare multiple conflicting extension methods. Say, you add a floo
extension method to both List
and Queue
, and then I write myQueueList.floo()
. It's unclear which one to pick. In case of a conflict, the usual approach is to pick the one with the most specific receiver type (prefer the one on List
over the one on Iterable
), and if there is no most specific receiver type, it's a static error.
C# and Kotlin both allow overriding by signature, so there risk of conflict is lower than it would be in Dart, but the same reasoning can apply.
So, Dart should also have scoped extension methods. We can probably get away with a Kotlin-like syntax, for example:
String String.double() => this + this;
Extension methods can be used on any type, including function types and FutureOr
(although probably not very practically on the latter). It can also be a generic function.
Example:
T List<T>.best<T>(bool preferFirst(T one, T other)) =>
this.fold((T best, T other) => preferFirst(best, other) ? best : other);
...
List<int> l = ...;
print(l.best((a, b) => a > b));
In this case, the type argument should probably be inferred from the static type of the receiver (which it wouldn't be if the receiver was just treated like an extra argument).
Another option is to allow:
T List<var T>.best(bool preferFirst(T one, T other)) => ...
Here the method is not generic, so the type variable is bound by the list, and it will use the actual reified type argument at run-time (and the static type at compile-time, which can cause run-time errors as usual).
Example:
List<T> List<var T>.clone() => new List<T>.from(this);
Then anyList.clone()
will create another list with the same run-time element type as anyList
.
It effectively deconstructs the generic type, which nothing else in Dart currently does. The extension method gets access to the reified argument type, just as a proper member method does, which is likely to be necessary for some functionality to be implemented in a useful way.
(Type deconstruction is a kind of pattern-matching on types, it might make sense in other settings too, like if (x is List<var T>) ... use T ...
).
This feature might not be possible, but if it is, it will be awesome 😄.
One issue with extension members is that they can conflict with instance members.
If someone declares T Iterable<T>.first => this.isEmpty ? null : super.first;
as a way to avoid state errors, it should probably shadow the Iterable.first
instance getter. The decision on whether to use the extension method is based entirely on the static type of the receiver, not whether that type already has a member with the same name.
Even in the case where you have a static extension method on Iterable
for a member added in a subclass, say shuffle
on List
, an invocation of listExpression.shuffle()
should still pick the static extension method, otherwise things are too unpredictable.
Another issue is that static extension methods cannot be invoked dynamically. Since dispatch is based on the static type, and you can't put members on dynamic
(or can you? Probably too dangerous), there is nothing to match against.
Or would putting a static extension method on Object
work for dynamic
expressions?
If so, there is no way out of an extension method on Object
, so perhaps casting to dynamic would be that out.
We say that static extension methods are available if the declaration is imported and in scope. It's not clear whether it makes any difference to import the declaring library with a prefix. There is no place to put the prefix, and Dart does not have a way to open name-spaces, which would otherwise make sense.
If we get non-nullable types, it might matter whether you declare int String.foo() => ...
or int String?.foo() => ...
. The latter would allow this
to be null
, the former would throw a NSMError instead of calling when the receiver is null
(and not a String
). Until such time, we would have to pick one of the behaviors, likely throwing on null
because it's the safest default.
We should support all kinds of instance members (so also getters/setters/operators). The syntax allows this:
int get String.count => this.runes.length;
void set String.count(int _) => throw 'Not really";
int String.operator<(String other) => this.compareTo(other);
We cannot define fields because the method is static, it doesn't live on the object.
We could allow field declarations to be shorthand for introducing an Expando
, but expandos don't work on all types (not on String
, int
, double
, bool
, or null
, because it would be a memory leak - mainly for String
, int
and double
- the value never becomes unavailable because it can be recreated with the same identity by a literal).
Metadata
Metadata
Assignees
Type
Projects
Status
Activity
willlarche commentedon Oct 14, 2018
This would be my preferred solution to
dart-lang/sdk#34778 and #40 . Coming from Objective-C, it's second nature for me to add methods to classes (and instances, but, I'll take what I can get.)
It feels much more elegant than #42 too. My main desired use case is adding constructors to classes from other libraries that my library configures constantly.
natebosch commentedon Oct 15, 2018
Would it be possible to add an extension method on a type with a specified generic argument?
For example, could I add
zoechi commentedon Oct 16, 2018
I have troubles seeing the benefit of
class Container { // Because all methods in C# must be inside a class
.They are not methods, they are static functions used in a way they look like methods, which means the class name is merely a namespace.
I expect code with #43 to be easier to read because it's more clear what's going on
in comparison to code where it looks like a method is called but it's actually something else.
lrhn commentedon Oct 16, 2018
I was definitely not advocating using the C# syntax, I was just showing as an example of an existing implementation (with the constraints enforced by that language).
Dart should definitely go with top-level syntax for extension methods, like in Kotlin, perhaps a syntax like the one I suggest later.
It's an interesting idea to add static methods to a class. There should be no problem with that:
It's simpler than adding instance methods, you don't have to worry about dispatching on the static type of the object. You still have to worry about conflicts.
I'm not sure whether adding static methods to a class is really that useful. After all, it's longer than just
countListElements
as a top-level function. It does introduce a potentially useful context to the use. More interestingly, adding a factory constructor would then be a simple extension.Since it has to be a factory, I guess the syntax could just be:
So,
factory
,static
or nothing would designate the operations as factory, static or instance.Seems possible.
As for specializing on type argument, that should also be possible.
The type to the left of the
.
is a type, not a class. It should work with any type, and the extension methods apply whenever the static type of the receiver of a member invocation is a subtype of the extension member receiver type. So, it can work, and probably should.My only worry is that it's too convenient - we can't do something similar with normal instance members, so I fear people will start using extension members for that reason. We should consider whether we'd want generic specialization of classes, even if it's just syntactic sugar for a run-time check.
munificent commentedon Oct 16, 2018
C# allows this and I have found it useful in practice, like the example you give. There are some interesting questions of how that interacts with subtyping. Is an extension method on
Iterable<num>
also available onIterable<int>
? Probably yes, but I'm not sure if that leads to confusing cases.It plays nice with auto-complete, which is one of the main way users discover APIs.
willlarche commentedon Oct 17, 2018
yjbanov commentedon Oct 17, 2018
This proposal could make Flutter's
.of
pattern auto-completable and probably more terse. E.g. you could auto-completecontext.
and getcontext.theme
,context.localizations
, and others as suggestions.131 remaining items
mit-mit commentedon Nov 14, 2019
Closing; this launched in preview in Dart 2.6. See the blog post:
https://medium.com/dartlang/dart2native-a76c815e6baf
willlarche commentedon Nov 14, 2019
🎉
kuhnroyal commentedon Dec 9, 2019
Is there an issue tracking the autocomplete/import in the Flutter IntelliJ plugin? Because this is not working at all.
EDIT:
Nvm this is probably a problem with the IntelliJ Dart plugin.
insinfo commentedon Apr 7, 2020
In the future, will it be possible to allow the addition of property with extension?
I have this use case, I currently use a custom list class implementation and I would like to use the extension to not need a custom class from the list just to add a simple property that allows to page results from the back end.
I use this implementation in this package https://pub.dev/packages/essential_rest
lrhn commentedon Apr 8, 2020
Static extension methods cannot change the shape of the object, so it cannot add more storage slots.
What you can already do is to use an
Expando
.Expandos don't work on strings, numbers, booleans or null, but they should be fine for lists.
You won't be able to use extension methods to avoid the custom class, though. Since
List
already has an[]
operator, you cannot override that with an extension method.maguro commentedon Apr 11, 2020
@eernstg , re "
Base.method()
", what is the rational for not supporting it?lrhn commentedon Apr 15, 2020
The rationale was that the affordances of the
on
type selection did not match well with acting like a real static method.You can write
extension on List<int>
, but you cannot writeList<int>.staticMethod
, so it's unclear whether a static extension static method declared in that extension would apply toList.staticMethod
, or to nothing.An extension, as currently defined by the language, is defined on a type. A static method is defined on a class (or mixin) declaration. For generic classes (or mixins), those are very different things. Some types have no corresponding class at all, some do. There was no clear an easy way to extend the static instance member functionality to also add static members to types, and there was no clear and easy way to restrict the declaration to extensions on "classes" because there is no such thing.
eernstg commentedon Apr 18, 2020
Agreeing with @lrhn that a type and a namespace are very different things, I'd just add that a feature for scoped injection of declarations into namespaces (e.g., adding static methods, class variables, factory constructors to a class) should probably be a completely separate construct. It might turn out to be useful with classes, import prefixes, and other targets, and it might involve regular declarations as well as declaration aliases. So it's basically a whole new design process rather than a twist on static extension methods.