-
Notifications
You must be signed in to change notification settings - Fork 205
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
Static Extension Methods #41
Comments
This would be my preferred solution to 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. |
Would it be possible to add an extension method on a type with a specified generic argument? For example, could I add num Iterable<num>.sum() => this.fold(0, (a,b) => a + b); |
I have troubles seeing the benefit of I expect code with #43 to be easier to read because it's more clear what's going on |
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: static int List.countElements(List<Object> o) => o.length;
...
List.countElements(someList)
... 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 factory String.reversed(String other) => ...; So, As for specializing on type argument, that should also be possible. |
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
It plays nice with auto-complete, which is one of the main way users discover APIs. |
The autocomplete is literally why I want it so badly.
…Sent from my iPhone
On Oct 16, 2018, at 17:45, Bob Nystrom ***@***.***> wrote:
Would it be possible to add an extension method on a type with a specified generic argument?
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 on Iterable<int>? Probably yes, but I'm not sure if that leads to confusing cases.
I'm not sure whether adding static methods to a class is really that useful.
It plays nice with auto-complete, which is one of the main way users discover APIs.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
|
This proposal could make Flutter's |
I think this would be awesome to have for libs like I like the Kotlin syntax where you just do Hopefully, with Dart going towards being a more strictly typed language, this is more possible than before? Are there any plans for this? I have seen it asked in a number threads. |
Yes, the shift to the new type system is absolutely what enables us to consider features like this now. They were basically off the table before.
No concrete plans yet, but lots of discussion and ideas (which is what this issue is part of). |
Are static extension getters/setters planned to be supported as well? Md5Sum String.get md5 => Md5Sum.from(this); I'm not sure what a reasonable use case of an extension setter would be, unless you count this example: void Stream<T>.set latest<T>(T value) => this.add(value);
myStream.latest = 4; // so you can add events via assignment?
myStream.latest = 6; How would type inference work for generic extension methods? Would it be possible to write: T T.get staticType<T> => T;
4.staticType; // int
(4 as num).staticType; // num Not sure if this is good or bad. :) I'm not entirely enthusiastic about adding deconstruction, also. For the sake of clearly being able to explain how the feature works, I think it's best to keep it a static concept, at least until there are other ways of doing runtime deconstruction in dart first. However I do see the issues in attempting to implement a function like I'm also not stoked about the interaction with
I don't see a clear win/win here which concerns me. Otherwise, I love it. |
Most Kotlin extension function for // Kotlin inline function (forEach) allows return of outer scope
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
} // Dart's forEach is not inlined and can't return outer scope
bool hasZeros(List<int> ints) {
ints.forEach((i) {
if (i == 0) return true; // forEach isn't inlined and can't return hasZeros here
});
return false; // always returns false
}
// for loops allow return but why should a for loop have more features than forEach?
bool hasZeros(List<int> ints) {
for (var i in ints) {
if (i == 0) return true; // returns from hasZeros
}
return false;
} |
Personally, I strongly feel that if we add extension methods, we should support all kinds of members: getters, setters, subscript, other operators. In my C# days, I often missed the lack of extension getters. |
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Is this really considered a good idea? It's largely a performance hack (basically a kind of macro), and it stops you from using actual functions as parameters. It's not that uncommon in Dart from what I've seen to pass named functions or tearoffs to forEach, which I think you can't do in Kotlin. |
Passing functions instead of a lambda is also supported in Kotlin. It also works for inlined functions. |
Ah, I see, it looks like you can use the |
What about factory/static extension too? factory MyClass.name() {
return MyClass(42);
}
static void MyClass.anotherName() {
} |
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
Most of these correctly are emitted as expressions that are non-dynamic. Notably: * Using collection literals (`[]`, `{}`) causes a dynamic call. * Using nested `*ngFor` causes a dynamic call. There are probably other ways to (accidentally) cause a dynamic call that otherwise appears to be static. We should continue to add examples, as these would block the use of advanced Dart language features such as dart-lang/language#41 or dart-lang/sdk#35084 in the template. PiperOrigin-RevId: 221693970
It's already available, on dart 2.6-dev |
when stable channel will hit 2.6 version it will be available in stable channel? |
I am having a lot of fun playing around with Dart 2.6.0-dev.8.0. The following example surprised me. Is this expected? class Base {}
extension Extension on Base {
static int method() => 42;
}
void main() {
Extension.method();
Base.method(); // Error: Method not found: 'Base.method'.
} |
Yes, |
Wait, static extensions are not supported? I didn't even see the discussion. 😱 That's very sad. It allows pretty powerful things. It's also very convenient with the // model.dart
class Model {} And, in the context of Flutter, add a // another-file.dart
import 'package:flutter/widgets.dart';
extension on Model {
static Model of(BuildContext context) {...}
} Only extension static methods allows this. |
maybe he should try
|
@renanyoy nice try. but that doesn't work. @rrousselGit I agree with Remi here. I too needed a static extension method and found this exact problem also. |
@rrousselGit why not creating an extension on class MyModel {
final String name = "Hello";
}
extension MyModelExt on Context {
MyModel get myModel => Provider.of<MyModel>(this);
}
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(context.myModel.name);
}
} |
Another point is support for generics. When the class you are extending has generics to be able to use those in your extension. extension IterableExtension on Iterable {
List<E> toImmutableList<E>() => List.unmodifiable(this);
}
/// outputs:
/// List<dynamic>
/// List<int>
/// List<int>
void main() {
final ex1 = [1, 2, 3].toImmutableList();
print(ex1.runtimeType);
final ex2 = [1, 2, 3].toImmutableList<int>();
print(ex2.runtimeType);
final ex3 = List<int>.unmodifiable([1, 2, 3]);
print(ex3.runtimeType);
} What I'd like is this.. but doesn't compile. extension IterableExtension2 on Iterable<T> {
List<T> toImmutableList() => List<T>.unmodifiable(this);
} |
@passsy That could work on small projects. But as the app grows, it's likely that we'll have two classes with the same name. With an extension static method, then we can use the import 'foo.dart' as foo;
import 'bar.dart' as bar;
foo.Model.of(context); // `.of` is a static extension on `Model`
bar.Model.of(context); // same here Whereas with extension getters/methods we'll have a name clash. It's also semantically better. That's how Flutter works. We do |
@slightfoot That's supported already: extension IterableExtension<T> on Iterable<T> {
List<T> toImmutableList() => List<T>.unmodifiable(this);
} |
Doh! |
I bet the current "default" - using static methods Name clashes are possible but you can also use // one.dart
extension One on String {
void hello() => print("Hey");
}
extension Two on String {
void hello() => print("Hello");
} import 'one.dart' show One;
import 'one.dart' as two show Two;
void main() {
"test".hello(); // Hey
// name clash, fallback to static method
two.Two("test").hello(); // Hello
} |
The discussions weren't always about that. There were a lot of mentions about "extensions allows adding any kind of member to a class", which includes static methods but also factory constructors (see #41 (comment)) extension on Foo {
factory foo() { ... }
} Although it's not supported either for some reason. |
Then I am wondering what is the benefit of supporting More concrete: In my case I have various libraries that would benefit from static method/factory extensions. In each case I have abstract base classes with various static/factory-methods that help the users to instantiate sub-classes. I'd like to split up the existing code into multiple libraries as well as allow others to add their own extensions. I don't think having the library users know the various extension classes is a viable solution. The most attractive workaround would be to introduce a separate factory object that can be extended and accessed from the base class, i.e. |
this is pretty much how C# works, extension methods are instance methods, not static methods ... I have wanted true static extension methods many times ... but the bigger use case does seem to be instance methods ... maybe next go around on dart |
Closing; this launched in preview in Dart 2.6. See the blog post: |
🎉 |
Is there an issue tracking the autocomplete/import in the Flutter IntelliJ plugin? Because this is not working at all. EDIT: |
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. extension TotalRecords on List {
int _totalRecords = 0;
int get totalRecords => _totalRecords;
set totalRecords(int totalRecords) {
_totalRecords = totalRecords;
}
} import 'dart:collection';
class RList<E> extends ListBase<E> {
final List<E> l = [];
RList();
@override
set length(int newLength) {
l.length = newLength;
}
@override
int get length => l.length;
@override
E operator [](int index) => l[index];
@override
void operator []=(int index, E value) {
l[index] = value;
}
int _totalRecords = 0;
int get totalRecords => _totalRecords;
set totalRecords(int totalRecords) {
_totalRecords = totalRecords;
}
} I use this implementation in this package https://pub.dev/packages/essential_rest |
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 extension TotalRecords on List {
static final _totalRecords = Expando<int>();
int get totalRecords => _totalRecords[this];
set totalRecords(int value) {
_totalRecords[this] = value;
}
} 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 |
@eernstg , re " |
The rationale was that the affordances of the You can write 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. |
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. |
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):
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:
The
String.
in front of the name marks this as an extension method onString
, and the body can access the receiver asthis
. 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 bothList
andQueue
, and then I writemyQueueList.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 onList
over the one onIterable
), 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:
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:
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:
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:
Then
anyList.clone()
will create another list with the same run-time element type asanyList
.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 theIterable.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, sayshuffle
onList
, an invocation oflistExpression.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 fordynamic
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() => ...
orint String?.foo() => ...
. The latter would allowthis
to benull
, the former would throw a NSMError instead of calling when the receiver isnull
(and not aString
). Until such time, we would have to pick one of the behaviors, likely throwing onnull
because it's the safest default.We should support all kinds of instance members (so also getters/setters/operators). The syntax allows this:
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 onString
,int
,double
,bool
, ornull
, because it would be a memory leak - mainly forString
,int
anddouble
- the value never becomes unavailable because it can be recreated with the same identity by a literal).The text was updated successfully, but these errors were encountered: