-
Notifications
You must be signed in to change notification settings - Fork 207
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 nested classes #336
Comments
This is a request for static nested classes. Effectively it only uses the outer class as a namespace, and it has the static members of the outer class in scope. There is nothing that can be done with this feature that cannot be done without, it merely provides a way to create nested namespaces for organizational purposes. I'd probably prefer that you had to prefix the class declaration with It's unclear whether instance members of For parsing, there should be no new issues. Because of prefixes, we already have to recognize We should also be allowed to nest other declarations, both mixins and typedefs. If we get general type aliases, we also have a way to provide un-nested access to a type for convenience. class Foo {
static class Bar {
}
}
typedef FooBar = Foo.Bar; |
I've been thinking about the ergonomics of widget/state separation in Flutter. Nested classes could provide a slightly more ergonomic solution if we introduced a different kind of stateful widget, along the following lines (I'm told class Counter extends NewKindOfStatefulWidget<_CounterState> {
static class _CounterState {
_CounterState(this.count);
final int count;
}
Counter(this.name);
final String name;
@override
_CounterState initState() => _CounterState(0);
Widget build(StatefulBuildContext<_CounterState> context) {
return Column(children: [
Text('$name: ${context.state.count}'),
FlatButton(
onTap: () { // this could take `context` to enable replay functionality, a la React hooks.
context.setState(_CounterState(context.state.count + 1));
},
child: Text('Increment'),
),
]);
}
} As @lrhn points out, because the nested class is static, its instance maintains no back-reference to the widget instance. In fact, it shouldn't. Otherwise you wouldn't be able to update widget configuration independently from state. So it's mostly syntactic sugar. There are a couple benefits though:
|
For Flutter's BLoC architecture It would be very useful. Now:
With nested classes:
There are 2 main advantages:
|
Any progress? |
is there a plan to support this please? |
There are no current plans to work on this. It is something we might consider in the future, but currently we have a pretty full slate of things to work on. |
I just stumbled upon this when trying to create a nested class full of const values, a sort of tree for static variables. An example for this could be a class, containing const fields for asset paths, so you have code-completion and a better overview of where your assets are being used, It would also make things more readable and allow for categorization of const values. See example Widget buildConstWidget(){
return const Center(
child: Image(
// instead of '/assets/images/foobar/coolpicture.png'
image: AssetImage(AssetPaths.images.foobar.coolpicture),
),
);
} The only way to do this currently would be with something like this class AssetPaths {
static const _ImagesFolder images = _ImagesFolder();
}
class _ImagesFolder {
const _ImagesFolder();
final launcher = const _ImagesLauncherFolder();
final login = const _ImagesLoginFolder();
final undraw = const _ImagesUndrawFolder();
}
class _ImagesFoobarFolder {
const _ImagesFoobarFolder();
final coolpicture = '/assets/images/foobar/coolpicture.png';
} This however prevents the usage of a I understand that nested classes are probably fairly complicated to implement and that you have to prioritize things, but I think having a feature for above use-case would be very beneficial for the maintainability for Dart/Flutter projects in the long term. Maybe this could be solved with a different language feature, such as manually defined namespaces or nested enums ? 🤔 Something like this namespace AssetPaths{
namespace Images {
class Folder1 {
static const foobar = 'assets/images/folder1/foobar.png'
}
class Folder2 {
static const baz = '/assets/images/folder2/baz.png'
}
}
}
// could then be referenced as `AssetPaths.Images.Folder1.foobar` and assigned to const Constructors |
The export "images.dart" as images;
export "videos.dart" as videos; And class Folder1 {
static const foobar = 'assets/images/folder1/foobar.png'
}
class Folder2 {
static const baz = '/assets/images/folder2/baz.png'
} That way, you'd be able to do: import "assets.dart" as assets;
var path = assets.images.Folder1.foobar; I haven't tested this but I think that should work. |
Narrator voice: "It didn't." You can't do |
Related issue for nested typedefs: #2952 |
Related: I'd also love to see support for enums inside classes. Something like: class Foo {
enum Mode { A, B, C };
void doSomething(Foo.Mode mode) ...
}
void main() {
new Foo().doSomething(Foo.Mode.B);
} |
Any updates on this? |
No updates, sorry. We've been busy with records, patterns, and views. |
This request feature of static nested classes is not be implemented, and may never get implemented, but as lrhn commented on Apr 30, 2019 it would be used for organizational purposes only. In exploring the possibility to make nested namespaces I found a pattern that might prove sufficient for some organizational purposes, (I think when dealing with constants or singletons, mainly). fee.dart
services.dart
anywhere.dart
A simpler solution to achieving the same thing is to allow the It's probably an antipattern but I thought I'd share it to show how those that have a natural tendency to make heavy use of namespacing because the enjoy the semantic nature of it might end up using dart (or bastardizing it) since it doesn't have a nested namespacing solution: in short, they'll end up rolling their own. |
Having nested classes allows you to implicitly set up parent/child relationships between container classes and their component classes, without having to register the components with the parents in some way. The component classes can simply directly access fields and methods in their parent. Class Outer {
final a = Inner('a');
final b = Inner('b');
final c = Inner('c');
final sharedSet = <String>{};
class Inner {
String name;
Inner(this.name);
void saveToSharedSet() {
sharedSet.add(name);
}
}
} Is there some other way to do this in Dart without manually registering each |
@lukehutch What you describe here is non-static inner classes, often just called "inner classes". You cannot create an An inner class can be desugared to a nested class by adding the implicit outer- Class Outer {
final a = Inner('a');
final b = Inner('b');
final c = Inner('c');
final sharedSet = <String>{};
static class Inner {
final Outer _outerThis;
String name;
Inner(this._outerThis, this.name);
void saveToSharedSet() {
_outerThis.sharedSet.add(name);
}
}
} and changing all And static nested classes can be desugared to separate classes which prefix the references to outer static members with the class name. (Where the real power would be is virtual nested classes that can be accessed through a type variable. But anything accessed through a type parameter is power we don't have today.) |
Any chance it will be looked at after Dart 3.0 release? Would be useful for BLoC state classes, especially with the new sealed functionality. sealed class MyBlocState {
class Loading extends MyBlocState {
}
class Data extends MyBlocState {
final String username;
Data(this.username);
}
} Usage: switch(bloc.state) {
MyBlocState.Loading() => LoaderWidget(),
MyBlocState.Data(var username) => Text(username),
} Of course, you could write them inline, but that pollutes global namespace. Also, having nested classes adds to self-documenting side of code. |
It's still an open issue, yes, but it's not clear if it's a particularly high priority one.
Yeah, this is a good point. We've definitely talked about having a nicer notation for defining an entire family of sealed classes in one place. Note, however, that in your example, the nested classes don't actually buy you much. All it lets you do is avoid writing sealed class MyBlocState {
}
class Loading extends MyBlocState {
}
class Data extends MyBlocState {
final String username;
Data(this.username);
}
switch(bloc.state) {
MyBlocStateLoading() => LoaderWidget(),
MyBlocStateData(var username) => Text(username),
} And now the use sites are actually a character shorter because they don't have the |
In that case, (veering off from the original proposal) what if we had a shorthand for declaring subclasses? sealed class MyBlocState {
subclass Loading {}
subclass Data {
final String username;
Data(this.username);
}
}
switch(bloc.state) {
MyBlocState.Loading() => LoaderWidget(),
MyBlocState.Data(var username) => Text(username),
} |
Yeah, that's more along the lines of what we've been talking about because here there is some real brevity benefit because you don't have to have the |
I'd like to share a new reason that this feature has become a higher priority for me: adopting graphql and leveraging the corresponding code-generated classes. In short, the generated classes tend to look like this: In greater detail... most (all?) of the current dart/flutter code generators leverage a "separator" to prefix the classes generated per query. You might be thinking: "this is just silly repeated code, these things could be refactored to share a single type across queries." But, that's actually not the case because of how graphql works. Suffice it to say, the types in a graphql schema provide what can be queried, but a It would be really nice to be able to organize these classes using static nested classes. E.g. this is much more readable: Anyway, thanks for keeping tabs on this. I suspect that any increase in adoption of graphql or any RPC ancestors/descendants that require this type of code generation would really benefit from this feature 👍🏻 |
Static inner classes essentially nested namespaces. The usual "zero, one or an infinite amount of any feature" applies. Dart currently allow one nesting of namespaces (which is why you can declare static variables and functions inside a class, but not classes, because that would introduce one further nested namespace.) So let's assume Dart does allow nested namespaces, meaning the ability to have any declaration as static inside a class/enum/mixin/extension/future-scope-declarations. Including Right off the bat, it should "just work". class Foo {
static class Bar {
static class Baz {
static int x = 42;
}
}
}
void main() {
print(Foo.Bar.Baz.x); // Just works!
} Lexical lookup is unsurprising. It's only static declarations, so there is never any argument about what So, technically, it's uncontroversial, and something that can easily be desugared to top-level declarations, where lexical references are desugared into explicitly prefixed references: class Foo {
static int x = 42;
class Bar {
void printX() { print(x); }
}
} gets desugared to class Foo {
static int x = 42;
}
class Foo$Bar {
void printX() { print(Foo.x); }
} and all references to Extensions would be considered imported if the top-level declaration that the extension is nested inside is imported. If that just works, is there something we'd want to add to that? We could add a So far, nesting is still entirely lexical. You cannot include something into a namespace without actually writing it there. But if we did, what would it be. Consider a declaration of If we think of it that way, do we want They would probably work, except that If we do that, do we need to guard against cyclic imports? It's probably not a problem to allow cyclic namespace structures. The links are named and the full structure doesn't have to be reified, so we can probably just not care. As long as you don't import something into itself. class Foo {
static class Bar {
static class Baz {
static import Foo show Bar;
}
}
} Here you can write class Foo {
static import Foo;
static int x;
} could probably be disallowed. But also not really an issue, because the local class Foo {
static export Foo show x;
static int x;
} Here the names coincide between the exported Do we allow a top-level TL;DR: Basic level of static declarations is essentially trivial, with only For the simple feature, my biggest worry is opportunity cost and complexity for readers. (Just allowing nested |
This seems good to me. No objections 😄
I'd get a lot of mileage out of just that but I don't hate some of the other suggestions here either. In particular this one:
This would be super useful for cases where I have a bunch of "namespaced" (static nested classes) and I want to import all of them into a file where the namespacing is understood and I don't need to worry about conflicts so I don't want to have to type the prefix constantly. E.g. in the graphql use-case I gave above, I'd still have a bunch of namespaced classes for the query types, but in the flutter
Good to hear.
As said above, this would seem very useful to my example use-case.
Re: opportunity cost, I hear that. I don't have a great sense for what else y'all are considering working on right now so I don't have a particularly useful opinion here. However, as a dart user, mostly in the context of flutter, I'm eager to see "sealed classes" (coming soon! yay!), "data classes" (being discussed), this namespacing thing, and a better story for json serialization/deserialization of user-defined types (maybe macros will enable this). Re: complexity for readers, I get this too, but I think if it's mainly just static nested classes that you provide then it's more likely to be a niche-but-useful feature than it is to become some sort of default thing folks do in all their code.
I can't tell if this would be able to solve my challenge or not. In any case, thanks for the braindump. I appreciate being able to see into your thinking on this. |
this... seems unecessary? you can already just define factory or static methods class Outer {
// riverpod does this
static const inner = Inner.new;
// or
factory Outer.inner(Some some, Parameter parameter) = Inner;
}
class Inner {}
main() {
// either option does the same.
final Inner inner = Outer.inner(some, parameters);
} having the only real benefit i see to this is that of organization, but then you suddenly have a giant class with many classes within that cant be split into multiple files... if you really want the namespace, you could just name it class Outer {
static typedef Inner = _OuterInner; // Outer.Inner();
}
// only accessable via Outer.Inner
class _OuterInner {} though the inner |
I think we really dont want to start adding class nesting for the same reason we extract widgets instead of having one massive widget with a massive horizontal scroll. Additionally, it will add confusion as users will expect the nested class to have access to the Outer class's methods and fields, which obviously wont work. It will muddy the visual scoping a lot. |
The Consider an example like abstract interface class Map<K, V> {
static final class Entry<K, V> {
final K key;
final V value;
// ...
}
// ...
Iterable<Entry<K, V>> get entries;
} which scopes the current top-level That's what's being asked for, and it cannot be simulated with current syntax. Allowing just nested types aliases means you can still do the same thing, you just need more syntax, declaring the type as top-level, probably private, then only providing access to it using the nested alias. |
I'm clearly not understanding this. It sounds like a serious recipe for bad code. i feel like it's much more likely to accidentally nest a class without meaning to And the thing is, a I believe that classes should always be top level. if you dont want that, it should be very deliberate to scope it with a static typedef. That way, the class is still clearly a normal class, and you can see at a glance what the parent class looks like, and what classes are scoped within it. otherwise you end up with an unreadable mess where you can barely tell what fields any one class has |
I also dont understand "nested extensions" this isnt a simple change, and i can easily see code generators completely breaking on these. |
Is being able to define a function inside a function a recipe for bad code?
Correct -- in Java you use an implementation The main reason for nesting classes, in my opinion, is to reduce namespace pollution. I would actually be fine with nested classes having the same visibility as underscore-prefixed classes, or even being visible only within the class they are defined in, sort of like a local variable is only accessible within the scope it is defined in. Personally the reason I want nested classes in Dart is simply scoping. In Java class nesting has quite a complex set of usecases, but the primary one is that a nested non-static class implicitly has access to the fields of the outer class it is defined within, and an instance of the inner class cannot be instantiated without an instance of the outer class. This defines a parent-child relationship between the classes. Honestly that has somewhat limited use, but there are times you want that. The other reason Java allows class nesting is to allow you to define more than one class in a source file (with static inner classes) -- which is not a limitation in Dart. |
Not inherently, but it's also very different. even if you couldnt do that, you could always make a closure variable, so it's a bit different.
Classes that only exist in a certain file already exists in Keep in mind that local-only classes would bulge out the class definition, which users cant use anyway, so why is it there? let it be a private class to begin with, so the public stuff remains visible and concise
Having a nested class that exists only in the class and has access to it's fields and methods is... weird. What's the point? I can already see that causing refactor-hell as a nested class becomes impossible to extract due to being too tied in. Way to easy to make bad code here, and with not much benefit. Not worth a language feature. Arguably just have a private class that takes I just don't see a strong enough usecase here that static typedefs wouldn't cover |
What is the status of this? |
I am also interested in this. I would allow constructing the state of views far more easily, such as what can be done in Jetpack Compose with nested class declarations. |
Allow declaring a class in the body of another class.
I'd expect that
Occupation
is not an instance variable ofPerson
, if that would even be a sensible notion in Dart, instead the outer class acts as a namespace containing the inner class. The inner class cannot be accessed from aPerson
instance. The inner class cannot capture instance variables from the outer class. It can howewer access static methods and fields.Nested classes could be used for:
StatefulWidget
s and their respectiveState
s)export _ as _
but without a library declaration fileThe text was updated successfully, but these errors were encountered: