-
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 #723
Comments
That wouldn't be the case.
|
There's still a difference between static methods and factories. Factories can do: abstract class Foo {
const factory Foo.myFactory(int a) = _FooImpl;
}
class _FooImpl implements Foo {
const _FooImpl(int a);
} Which is the only way to have const constructors on factory. That's a bit off-topic though. |
As has been pointed out, Class declarations introduce a namespace too, and for a generic class, the distinction between a type and the class is fairly big. The class declaration That is one reason for not allowing static methods of extension declarations to be accessible on the We could consider new syntax to actually inject static members into other classes, say [!NOTE] If we have extension statics, then it's possible to declare a static member with the same name as an instance member on the same type, as long as at least one is an extension member. |
That request doesn't ask for allowing static methods on I do agree that it doesn't make sense to use the Does your comment apply to |
Adding extension MyEvent on CustomEvent {
factory CustomEvent.myEvent(String message) {
return CustomEvent('my-event', message);
}
String get message {
assert(type == 'my-event', 'CustomEvent is not 'my-event');
return detail as String;
}
} I was also thinking of using it for a extension JsonRequest on Request {
factory Request.json(Map<String, dynamic> body) {
return Request(jsonEncode(body), headers: { 'Content-Type': 'application/json' });
}
} |
A If you define: extension X on Iterable<int> {
factory X.fromList(List<int> integers) => ... something ...;
} then what would it apply to? Most likely it would only apply to The All in all, I'd prefer something like: static extension Something<T> on ClassType<T> {
static int method() => 42;
factory Something.someName() => new ClassType<T>.actualConstructor();
} which effectively adds static members and constructors to a class declaration, and can be hidden and imported just like extensions. It can probably do one thing normal constructors cannot: Declare a constructor only on a subtype, so: static extension X on List<int> {
factory X.n(int count) => List<int>.filled(count, 0);
}
... List<int>.n(5) ... (I would support adding extra restrictions to type arguments in constructors in general too.) |
Nice catch on the subclass thing. For me, factory/static extensions should be applied only on the extact match of the Such that with: static extension on A {
factory something() = Something;
static void someMethod() {}
} Would be applied only on
What about requiring a typedef, and applying the extension only typedefs then? Because typedefs makes it definitely useful. I would expect being able to write: typedef VoidCallback = void Function();
static extension on VoidCallback {
static empty() {}
const factory delayed(Duration duration) = CallableClass;
}
VoidCallback foo = VoidCallback.empty;
const example = VoidCallback.delayed(Duration(seconds: 2)); Currently, Dart lacks a way to namespace functions utils |
@tatumizer With such syntax, how would you represent generic extensions? static extension<T extends num> on List<T> {
factory example(T first) => <T>[first];
} |
If the static extension itself does not have a name, then it's not possible to |
I think static extension methods would somewhat help with #746. However, there may be a couple of things that might be confusing, like how type inference affects constructor availability. e.g. static extension IntList on List<int> {
factory List<int>([int length]) => List.filled(length ?? 0, 0);
}
static extension NullableList<T> on List<T?> {
factory List<T?>([int length]) => List.filled(length ?? 0, null);
} etc. For constructor availability, it feels weird to have to specify the type to get the correct factory constructors. For example: List<int> foo;
foo = List(123); Also, if you have to disambiguate between multiple extensions, how would the syntax look? |
I just wanted to voice support for adding static members to an existing class. For what it's worth I don't care about For context, I think this would help improve certain code generators that want to generate top-level members based on an existing types. Rather than requiring users to remember a particular naming convention to find these members, they could instead be added as extensions to their associated class. For example, AngularDart compiles developer-authored components into a number of view classes. We expose a // Developer-authored
// example_component.dart
@Component(...)
class ExampleComponent { ... } // Generated
// example_component.template.dart
ComponentFactory<ExampleComponent> createExampleComponentFactory() => ...; // main.dart
import 'example_component.template.dart';
void main() {
// User has to remember this naming convention.
runApp(createExampleComponentFactory());
} Ideally we could declare these // Generated
// example_component.template.dart
static extension ExampleComponentFactory on ExampleComponent {
ComponentFactory<ExampleComponent> createFactory() => ...;
} // main.dart
import 'example_component.template.dart';
void main() {
runApp(ExampleComponent.createFactory());
} |
I was just adding extensions to one of my library. first i tried to move factory methods to an extension but got error that factory are not supported and i was like that's fine. Then tried to convert factory constructor to static functions with factory annotations, the IDE did not complain but the client code using the extension did complain. I was expecting static methods to be supported because most of the language i have used support it... |
Also the current behavior of defining static extension methods is not clear. example: Previous API definition. class ExecutorService {
factory ExecutorService.newUnboundExecutor([
final String identifier = "io_isolate_service",
]) => IsolateExecutorService(identifier, 2 ^ 63, allowCleanup: true);
} With the new extension feature. extension ExecutorServiceFactories on ExecutorService {
@factory
static ExecutorService newUnboundExecutor([
final String identifier = "io_isolate_service",
]) => IsolateExecutorService(identifier, 2 ^ 63, allowCleanup: true);
} Currently the client code have to call it this way: ExecutorServiceFactories.newUnboundExecutor(); But i think the syntax should be: ExecutorService.newUnboundExecutor(); It's more common for people that are use to extensions in other languages, it's more clear on which type its applied, its does not create confusion in client code, it's does not the break the API of library and does not require code change on client side. |
I'm not sure if it has been mentioned yet. Adding this support would increase the set of changes which are breaking. As of today it is not a breaking change to add a static member on a class. If we give the ability to add members to the static interface of a class we need to come up with how to disambiguate conflicts. If we disambiguate in favor of the class it's breaking to add a static member, because it would mask a static extension. I'm not sure all the implications of disambiguating in favor of the extension. |
At this point, the extension is useless, since it's just a class with static members. extension PlatformX on Platform {
static bool get isDesktop =>
Platform.isMacOS || Platform.isWindows || Platform.isLinux;
static bool get isMobile => Platform.isAndroid || Platform.isIOS;
} class PlatformX {
static bool get isDesktop =>
Platform.isMacOS || Platform.isWindows || Platform.isLinux;
static bool get isMobile => Platform.isAndroid || Platform.isIOS;
} |
The purpose of using extensions for static methods and factories is to regroup everything in a natural way. Sure, we could make a new placeholder class. |
I found this problem when making a flutter plugin. I plan to put static methods and static callback Function members in the same class for the convenience of users, but on the other hand, I want to move the callback to another file to Improve readability. I found that dart 2.6 supports extensions. I thought it was similar to swift, but when I started to do it, I found various errors. After searching, I regret to find that static method extensions are not supported.🥺 extension ZegoEventHandler on ZegoExpressEngine {
static void Function(String roomID, ZegoRoomState state, int errorCode) onRoomStateUpdate;
} void startLive() {
// Use class name to call function
ZegoExpressEngine.instance.loginRoom("roomID-1");
}
void addEventHandlers() {
// I want to use the same class to set the callback function, but it doesn't work
// ERROR: The setter 'onRoomStateUpdate' isn't defined for the class 'ZegoExpressEngine'
ZegoExpressEngine.onRoomStateUpdate =
(String roomID, ZegoRoomState state, int errorCode) {
// handle callback
};
// This works, but requires the use of extended aliases, which is not elegant
ZegoEventHandler.onRoomStateUpdate =
(String roomID, ZegoRoomState state, int errorCode) {
// handle callback
};
} At present, it seems that I can only use the extension name to set the callback function, which can not achieve the purpose of letting the user only pay attention to one class.🧐 |
Definitely agree that the static method should be called from the original class and not the extension class. |
@rrousselGit I think the same, in my case the intention is to use to apply Design System without context doing this way: extension ColorsExtension on Colors {
static const Color primary = const Color(0xFFED3456);
static const Color secondary = const Color(0xFF202426);
static const Color backgroundLight = const Color(0xFFE5E5E5);
static const Color backgroundDark = const Color(0xFF212529);
static const Color warning = const Color(0xFFFFBB02);
static const Color warningBG = const Color(0xFFFFFCF5);
static const Color confirm = const Color(0xFF00CB77);
static const Color confirmBG = const Color(0xFFEBFFF7);
static const Color danger = const Color(0xFFF91C16);
static const Color dangerBG = const Color(0xFFFEECEB);
static const MaterialColor shadesOfGray = const MaterialColor(
0xFFF8F9FA,
<int, Color>{
50: Color(0xFFF8F9FA),
100: Color(0xFFE9ECEF),
200: Color(0xFFDEE2E6),
300: Color(0xFFCED4DA),
400: Color(0xFFADB5BD),
500: Color(0xFF6C757C),
600: Color(0xFF495057),
700: Color(0xFF495057),
800: Color(0xFF212529),
900: Color(0xFF162024)
},
);
}
|
@faustobdls In that case, it seems pretty counterintuitive to me to do what you are proposing for these two reasons:
|
@creativecreatorormaybenot my intention is the use of this for flavors within the company, definition of design system and etc, besides that this is just a case, we can mention others that depend on the fact that the |
Any news? Very necessary feature |
@creativecreatorormaybenot Extension methods as a whole are convenience/style feature. Since they were implemented they should be implemented right, and this is missing. Also nobody said it would be prioritized over NNBD. Don't worry, NNBD will not be delayed because of this issue, if that's what you fear, somehow. |
Another idea. How about allowing extensions on multiple types, and on static declaration names, in the same declaration: extension Name
<T> on List<T> {
.... normal extension method ...
} <T> on Iterable<T> {
...
} <K, V> on Map<K, V> {
...
} on static Iterable { // static methods.
Iterable<T> create<T>(...) =>
} I'd still drop constructors. It's to complicated to make the distinction between a constructor on a generic class and a generic static method. Allowing that does not carry its own weight. By combining the declarations into the same name, we avoid the issue of having to import/export/show/hide two or more names for something that's all working on the same types anyway. Having the same name denote different extensions does make it harder to explicitly choose one. |
There is another unexpected problem extension methods cause, but this is also one they could, with @rrousselGit proposal, fix. The problem is that extension methods only exist when the generic type argument is known. But in a generic class' constructor we don't know the concrete type. I try to explain: I was trying to write a thin wrapper type for Dart's FFI: class Array<T extends NativeType> {
Pointer<T> ptr;
List _view;
Array(int length) // Problem: ptr.asTypedList() only exists with known type T
}
With @rrousselGit proposal this problem could be easily solved: class Array<T extends NativeType> {
Pointer<T> ptr;
List _view;
Array._(this.ptr, this._view);
}
extension Uint8Array on Array<Uint8> {
Uint8List get view => _view;
factory Array(int length) {
final ptr = allocate<Uint8>(count: length);
return Array._(ptr, ptr.asTypedList(length));
}
}
extension Int16Array on Array<Int16> {
Int16List get view => _view;
factory Array(int length) {
final ptr = allocate<Int16>(count: length);
return Array._(ptr, ptr.asTypedList(length));
}
}
Array<Uint8>(10); // works
Array<Int16>(10); // works
Array<Uint32>(10); // doesn't work
Array(10); // doesn't work But for now I have to use static extensions which are a heavy burden on users: extension Uint8Array on Array<Uint8> {
Uint8List get view => _view;
static Array<Uint8> allocate(int length) {
final ptr = allocate<Uint8>(count: length);
return Array._(ptr, ptr.asTypedList(length));
}
}
extension Int16Array on Array<Int16> {
Int16List get view => _view;
static Array<Int16> Array(int length) {
final ptr = allocate<Int16>(count: length);
return Array._(ptr, ptr.asTypedList(length));
}
}
Uint8Array.allocate(10);
Int16Array.allocate(10); This is just confusing because a user would think Subclassing would not be a good solution, because then Uint8Array arr = Uint8array.allocate(10); // synonymous to Array<Uint8> arr;
|
I have the same issue while wanting to add static method as I would like to have something like this: User user = await User.get(db); But currently I have to: User user = await User().get(db); |
Because static extensions are not supported, we cannot create app_checks.dart for ZulipApp.ready or ZulipApp.scaffoldMessenger. See: dart-lang/language#723
Because static extensions are not supported, we cannot create app_checks.dart for ZulipApp.ready or ZulipApp.scaffoldMessenger. See: dart-lang/language#723
Because static extensions are not supported, we cannot create app_checks.dart for ZulipApp.ready or ZulipApp.scaffoldMessenger. See: https://g ithub.com/dart-lang/language/issues/723
I find it really unintuitive that members defined in an extension apply to the on-type... except static members. When I write extensions, they're designed to be invisible, but defining a static method means someone now needs to know the name of my extension.
So static members in extensions with such an on-type can just be an error? I find static extensions would be really helpful for That's actually not any worse than
That's already how static members work today, so I wouldn't see that as so disruptive. To avoid breaking how people may be using static extension members today, how about keeping the static members on the extension's namespace, and in the future allowing it also be used in the on-type's namespace? |
The proposal about static extensions that I mentioned here is currently being revised, in this PR. That PR adds two alternative proposals. One of them ('...variant1.md'') is using a new kind of declaration @Levi-Lesches wrote:
Using variant2, only some extensions have an on-class (or an on-mixin, etc.), and only an extension that has an on-class (etc) is capable of "injecting" static members and constructors into a different declaration (namely the given class/mixin/... declaration which is the on-class/mixin/...). For other extensions (e.g., the ones whose on-type is a function type), it is still allowed to declare static members and they can still be invoked like today (using That seems compatible with your proposal above. It shouldn't actually be a problem to inject static members into a type alias for a type that could not have been the on-class/mixin/... of a static extension, e.g., a function type or a record type. The proposal currently does not include this, but can be added in the future. Even
extension E<X> on List<X> {
factory List.fromList(List<X> from) => ... something ...;
}
void main() {
var xs = List<int>.fromList([]);
} I don't see how it could be acceptable for an expression of the form This is not technically a requirement, any new kind of expression could evaluate to an object of any type whatsoever, we just need to specify it in that way. However, I expect that it will be highly inconvenient if we "can't trust the type" of an instance creation. That is, all constructor invocations have the static type we can read off of the expression itself, generative constructors return something of exactly that type, and factories return something whose run-time type may also be a proper subtype of the static type (for soundness, it couldn't be an unrelated type). I think we should preserve that property. class A {}
class B extends A {}
extension AExtension on A {
A.generative(): this();
factory A.factory() => B();
}
void main() {
print(A.generative().runtimeType); // 'A', not some subtype.
print(A.factory().runtimeType); // 'B', which is a subtype, but that's OK for a factory.
}
Sounds like a description of the proposals I've mentioned. Being able to call the static members using the extension name as the syntactic receiver is useful because it can resolve a name clash. extension E1 on int {
static void foo() {}
static void bar() {}
}
extension E2 on int {
static void foo() {}
}
void main() {
int.foo(); // Compile-time error, ambiguous.
E1.foo(); E2.foo(); // OK.
int.bar(); // OK.
} |
Awesome! I did see |
What do you guys think the status of this issue is? Or what the timeline for resolving this issue might be? Is this something that will be included in an upcoming release? Because lately, there has been a lot more activity on this issue, and it's already 5 years old. |
True. No promises, but this topic currently has the attention of the language team. I think it's likely to be accepted in some form (after a lot of discussion, of course). |
I don't like What if we reused a keyword elsewhere? extension on int {
on static Handler handler = ...;
}
extension on List<int> {
// Errors - generic instantiations don't support on static extensions
// Leaves room for future support
on static Handler handler = ...;
} |
Also generalize ValueNotifierChecks to ValueListenableChecks. We can't write checks-extensions for ZulipApp.scaffoldMessenger or ZulipApp.ready, though, because Dart doesn't have extensions for static members. Apparently they're currently working on them: dart-lang/language#723 Co-authored-by: Zixuan James Li <zixuan@zulip.com>
@eernstg:
Then how are you going to inject the methods from the subclass (to enable the shortcut mechanism) avoiding name clashes? Name clashes for static methods are common, and you always have at least one name clash (unnamed constructor), but in general, you can have more. Hence the question. |
Static extensions aren't inherited. They would be only on the on type. No super types, no subtypes. Not until we get static inheritance anyway, then we would apply it to that instead. |
My question was about injection planned as part of "shortcut" propolsal under discussion. Consider And there's never one cockroach! In general, there can be N conflicting definitions. What to do about them? The approach suggested by the proposal is not consistent with how "normal" extensions work. They allow conflicting definitions (which would be silently ignored, unless you invoke them explicitly using the extension name as a prefix: In another thread, I suggested a second namespace (e.g. static extension AlignmentGeometryExt on AlignmentGeometry {
// ,,,
} where |
Static extensions are not intended for the person who wrote the original class. There is no second namespace, there are extensions on that one namespace, just like existing extension members extend the instance interface, and actual instance interface members take precedence. If you want to change what an existing constructor on a class does, you have to be the person owning the code. You cannot do that using static extensions. That is simply not a design goal. Static extensions are due extending, not rewriting. (Static extensions likely cannot declared non-redirecting generative constructors, so those all have to be in the original class.) Also, I have no problem allowing an extension to declare a member with the same base name as a static member of the |
@tatumizer wrote, in response to a remark in the static extension proposal here:
The remark about 'should not have name clashes' refers to a recommended warning. In other words, you can have a name clash, and it is not an error, but you may get a warning (depending on what the analyzer team decides to do in this situation). But the point is that you should get that warning in the following case: class C {
static void foo() {}
}
static extension EC on C {
static int get foo => 42; // Warning, `C` already declares a static member named `foo`.
} The reason why we'd want this warning is that we can't use If we want to inject some static members / constructors into a class then we must ensure that they don't have name clashes. This means that we'd need some kind of rename mechanism if we want to inject two declarations with the same name into a common superclass (or, indeed, any class). This is a built-in capability of some mechanisms, including redirecting factories. For instance, in the standard example where we wish to add constructors from static extension EdgeInsetsGeometryExtension on EdgeInsetsGeometry {
const factory EdgeInsets .fromLTRB = .fromLTRB;
const factory EdgeInsets .all = .all;
const factory EdgeInsets .only = .only;
const factory EdgeInsets .symmetric = .symmetric;
static const zero = EdgeInsets.only();
const factory EdgeInsetsDirectional .fromSTEB = .fromSTEB;
const factory EdgeInsetsDirectional .onlyDirectional = .only;
const factory EdgeInsetsDirectional .symmetricDirectional = .symmetric;
const factory EdgeInsetsDirectional .allDirectional = .all;
static const zeroDirectional = EdgeInsetsDirectional.only();
} This example assumes that we have #4135 (such that we can omit the formal parameter lists) and #4153 (such that we can specify the return type, and #4144 (such that we can omit the target class name
Does this mean "how can we inject stuff from static extension on AlignmentGeometry {
const factory Alignment .someName = .new;
} We already have a constructor whose name is With some variant of #357, we can then use it like It seems quite likely that
Sounds like you'd be much happier if you could simply say
There is no difference. The proposals here do allow a static extension to declare a static member or constructor whose name clashes with a declaration in the class A {
static int foo = 1;
}
static extension AE on A {
static String foo = 'Hello';
}
void main() => AE.foo.substring(1); // OK.
That is not quite accurate. A crucial property of the name of a static extension is that it allows for the static extension to be imported (or hidden), whereas the "nameless" static extension has a private name (which means that it is local to the declaring library). |
Pretending can get us a long way, right until we have to actually choose My guess is that as a general rule, you propose to add the suffix I also have issues with the fact that each "borrowed" method now belongs to 3 +1 namespaces. Using EdgeInsets.all(...);
EdgeInsetsGeometry.all(...);
EdgeInsetsGeometryExtension.all(...);
.all(...); // with a context type EdgeInsetsGeometry Don't get me I tried to come up with the alternative: e.g. declare one of the classes as "default", and use the notation (another option is to admit that |
Adding extra syntax for defaults ($) doesn't make much sense. I sort of see what you're saying, but also not really. If it doesn't work as a default, or doesn't have a reasonable name, then you should just use the "real" one. Be specific when you want something specific. That said, it's not as if you couldn't namespace things with an object like API design is not something we should argue about, and we should remember that shoehorning shortcuts where they don't make sense only hurts everyone. Static typedefs (like a discount nested class) can be a nice synergy feature later on too. |
Name mangling is a non-starter IMO - it's aethetically ugly, changes the API, etc. It still relies on the notion of "default implementation" (we inject the methods from it - so no "directional" methods there), but ithe latter gets de-emphasized with this treatment (the wording " (This entails the syntax Disambiguation via |
That doesn't make much sense though. If you need something on B that doesn't fit on A, just use B. You simply won't know where $ even comes from. At least . Is on the actual context type, which is easy to find. Don't add magic. Also, changing the API is fine as long as you're smart about it. If the name is too badly mangled, then don't add it, or consider other patterns, like a nested factory ie |
What "thing"? 😄 |
@tatumizer wrote:
True, naming is hard. But we can't expect any language mechanism to do anything substantial about this. I think the main issue here is software evolution vs. initial software designs. If shorthands based on the context type had been available when the classes That wasn't the case and now some really good constructor names in the static namespace of Similarly, if the shorthands had been on the table when this hierarchy was designed then we probably wouldn't have had two constructors named We simply can't expect to avoid naming issues when the language evolves, there will be situations where a certain approach to naming turns out to be suboptimal later on, when the language adds new features. Note that it would be possible to have several declarations of static extensions that are injecting constructors into
That's a good point! However, having
The abbreviated one, using
I wouldn't even begin to try to solve this kind of problem in general. Naming is hard! ;-) .. and we all have to deal with it in concrete ways, in concrete code. |
Naming is out of scope of this issue though, if we only get the benefit in new classes or distant-future breaks, then so be it. (Of course we would also get the benefit for enums and pre-existing static members, which itself can be enough to justify the issue) We can't expect the feature to be exactly perfect everywhere at the first go can we? Let the API developers figure out if they want to set certain defaults themselves, and if anyone else wants to add more via static extensions, then all the power to them. Naming is on them, not the feature. We should try to remember not to get lost in the sauce, even if the shorthand may not be useful everywhere, it will be useful in enough places, and future API design will only improve that. Not everything has to fit through the square hole. Not all problems are nails. (When all you got is a hammer(shorthand)) |
Motivation
Currently, extension methods do not support adding static methods/factory constructors. But this is a missed opportunity!
There are many situations where semantically we want a static method/factory, but since the type is defined from an external source, we can't.
For example, we may want to deserialize a
String
into aDuration
.Ideally, we'd want:
But that is currently not supported.
Instead, we have to write:
This is not ideal for the same reasons that motivated extension methods. We loose both in discoverability and readability.
Proposal
The idea is to allow
static
andfactory
keyword inside extensions.Factory
Factories would be able to capture the generic type of the extended type, such that we can write:
Which means we'd be able to do:
But not:
Factories would work on functions and typedefs too (especially after #65):
Static members
Using extensions, we would be able to add both static methods and static properties.
They would not have access to the instance variables, nor the generic types.
On the other hand, static constants would be allowed.
We could, therefore, extend Flutter's
Colors
to add custom colors:Or
Platform
to add customisWhatever
:which, again, would work on functions and typedefs
The text was updated successfully, but these errors were encountered: