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

Static nested classes #336

Open
dz4k opened this issue Apr 29, 2019 · 32 comments
Open

Static nested classes #336

dz4k opened this issue Apr 29, 2019 · 32 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@dz4k
Copy link

dz4k commented Apr 29, 2019

Allow declaring a class in the body of another class.

class Person {
  class Occupation {
    String jobTitle;
    Occupation(this.jobTitle);
    String named(String name) =>
      moniker(Person(name, this));
  } 
  String name;
  Occupation occupation;
  Person(this.name, this.occupation);
  static String moniker(Person p) =>
      '${p.name} The ${p.occupation.jobTitle}';
}
Person p = Person("Rosie", Person.Occupation("Riveter"));

I'd expect that Occupation is not an instance variable of Person, 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 a Person 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:

  • Indicating a relationship between classes / encapsulating classes which only serve to complement another (e.g. Flutter StatefulWidgets and their respective States)
  • Creating arbitrary namespaces like export _ as _ but without a library declaration file
  • Encapsulating many related methods under a namespace
@lrhn
Copy link
Member

lrhn commented Apr 30, 2019

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 static so we reserve the possibility of non-static nested classes.

It's unclear whether instance members of Person are in the lexical scope of Occupation members, since Dart traditionally only has one scope and instance members are in scope inside static members, you just can't use them. The same thing should work just as well (or as badly) for static nested classes.

For parsing, there should be no new issues. Because of prefixes, we already have to recognize id1.id2 as a type in declarations. We now have to recognize id1.id2....idn to arbitrary depths, but that is not a problem.

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;

@lrhn lrhn added the feature Proposed language feature that solves one or more problems label Apr 30, 2019
@yjbanov
Copy link

yjbanov commented Apr 30, 2019

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 flutter_hooks is doing something similar):

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:

  • Nesting the state class inside the widget class strongly visualizes the relationship between the widget and its state. I think it improves readability.
  • The state class can be made private to the widget. While Dart currently lacks class-level privacy, we could add it via package:meta that would rely on the nested class syntax.

@AlexanderFarkas
Copy link

AlexanderFarkas commented Sep 18, 2020

For Flutter's BLoC architecture It would be very useful.
With nested classes developers would not prefix event/state classes to avoid ambiguity.

Now:


import 'package:bloc/bloc.dart';

class AuthorizationScreenBloc extends Bloc<AuthorizationScreenEvent, AuthorizationScreenState> {
  AuthorizationScreenBloc(AuthorizationScreenState initialState) : super(initialState);

  @override
  Stream<AuthorizationScreenState> mapEventToState(AuthorizationScreenEvent event) async* {
    throw UnimplementedError();
  }
}

abstract class AuthorizationScreenEvent {}
class SignInAuthorizationScreenEvent extends AuthorizationScreenEvent {}

abstract class AuthorizationScreenState {}
class SuccessAuthorizationScreenState extends AuthorizationScreenState {}

With nested classes:

import 'package:bloc/bloc.dart';

class AuthorizationScreenBloc extends Bloc<AuthorizationScreenEvent, AuthorizationScreenState> {
  AuthorizationScreenBloc(AuthorizationScreenState initialState) : super(initialState);

  @override
  Stream<AuthorizationScreenState> mapEventToState(AuthorizationScreenEvent event) async* {
    throw UnimplementedError();
  }
  static class SignInEvent extends AuthorizationScreenEvent {
    
  }
  
  static class SuccessState extends AuthorizationScreenState {
    
  }
}

abstract class AuthorizationScreenEvent {}
abstract class AuthorizationScreenState {}

There are 2 main advantages:

  1. I can see available events in my IDE, while typing AuthorizationSreenBloc.(dot) (So I'm not compelled to always check Bloc class for events)
  2. I should not prefix all the events and states (it's too cumbersome in some blocs)

@dagba
Copy link

dagba commented Sep 23, 2020

Any progress?

@FrantisekGazo
Copy link

is there a plan to support this please?

@leafpetersen
Copy link
Member

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.

@lukaskurz
Copy link

lukaskurz commented Oct 5, 2021

I just stumbled upon this when trying to create a nested class full of const values, a sort of tree for static variables.
Since const fields are not allowed, I don't see any possibility to do this right now, with the strings being const too.

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 const constructor in the example above, since the fields are not actually const but final.

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

@Levi-Lesches
Copy link

The as keyword when used with imports and exports is pretty much the "namespace" functionality you're talking about. Maybe you can have a file called assets.dart:

export "images.dart" as images;
export "videos.dart" as videos;

And images.dart would have:

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.

@lrhn
Copy link
Member

lrhn commented Oct 5, 2021

@Levi-Lesches

haven't tested this but I think that should work.

Narrator voice: "It didn't."

You can't do export ... as. The import namespaces you get from import ... as only works within the library it's imported in, but only actual declarations can be exported. There is currently no way to nest namespaces.

@mit-mit mit-mit changed the title Nested classes Static nested classes Oct 8, 2021
@mit-mit
Copy link
Member

mit-mit commented Oct 11, 2021

Related issue for nested typedefs: #2952

@bobjackman
Copy link

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);
}

@SergeyShustikov
Copy link

Any updates on this?

@munificent
Copy link
Member

No updates, sorry. We've been busy with records, patterns, and views.

@lastmeta
Copy link

lastmeta commented Nov 23, 2022

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

class _Fee {...}
class fees {
  static const fast = _Fee(...);
  static const standard = _Fee(...);
  static const slow = _Fee(...);
}

services.dart

export 'fee.dart';

anywhere.dart

import 'services.dart' as services;
print(services.fee.standard);

A simpler solution to achieving the same thing is to allow the export ... as ... semantics.

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.

@lukehutch
Copy link

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 Inner class with the Outer class instance?

@lrhn
Copy link
Member

lrhn commented Feb 23, 2023

@lukehutch What you describe here is non-static inner classes, often just called "inner classes".
Every instance of such an inner class has a reference to an instance of the outer class.

You cannot create an Inner without having an Outer, cannot do new Outer.Inner("foo"), you have to do new someOuterObject.Inner("foo").

An inner class can be desugared to a nested class by adding the implicit outer-this reference explicitly.

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 outer.Inner(foo) invocations to Outer.inner(outer, foo) instead.

And static nested classes can be desugared to separate classes which prefix the references to outer static members with the class name.
It's still all about organization and convenience, there is no actual extra power in this.

(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.)

@AlexanderFarkas
Copy link

AlexanderFarkas commented Apr 5, 2023

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.

@munificent
Copy link
Member

Any chance it will be looked at after Dart 3.0 release?

It's still an open issue, yes, but it's not clear if it's a particularly high priority one.

Would be useful for BLoC state classes, especially with the new sealed functionality.

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 MyBlocState at the beginning of the name of each class declaration. You could just as easily do:

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 .. I do like how the classes look with shorter names and having them sort of "namespaced" to the parent class. But at least in this example, it's not a huge win.

@dz4k
Copy link
Author

dz4k commented Apr 5, 2023

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),
}

@munificent
Copy link
Member

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 extends MyBlocState on each subclass. On the other hand, it would be nice if this could work with subclasses that either extend or implement, or maybe even mix in the surrounding class/mixin.

@samandmoore
Copy link

samandmoore commented May 2, 2023

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: Options$Query$FetchPerson and Variables$Query$FetchPerson (source). In fact, these name are some of the less ugly ones. It gets nastier when you have deeply nested fields in your queries and type unions.

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 type Person with 2 fields, name and age can produce 3 possible resulting classes (in shorthand) { name, age }, { name }, and { age }. As a result, the common pattern is to produce a unique class for Person per query, based on the fields selected, and defined in a nested style using a separator, e.g. Query$FetchPerson$Person.

It would be really nice to be able to organize these classes using static nested classes. E.g. this is much more readable: Query.FetchPerson.Person. The ease on the eyes is one benefit for sure, but also the potential for smart import support that could make it so the prefixing isn't necessary within the context of a specific file would be amazingly useful for readability as well as carpal tunnel 😅

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 👍🏻

@lrhn
Copy link
Member

lrhn commented May 2, 2023

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 typedef.

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 this will refer to.
(I'd also require the static for nested declarations, just in case we want to, one day, allow non-static nested declarations.)
Name-space conflicts are resolved as usual, as any other named static member. (Which means that you cannot have a static class named Foo directly inside another class named Foo, because we disallow that. Mainly because it conflicts with the syntax of constructors, it's not like there would be any real ambiguity, and we could probably allow it if we really wanted to.)

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 Foo.Bar are converted to Foo$Bar.

Extensions would be considered imported if the top-level declaration that the extension is nested inside is imported.
That actually does suggest that we'd want to be able to hide Foo.Ext on an import, to avoid the extension, without
hiding Foo. You'd still be able to write Foo.Ext directly, so it's only a way to make an extension count as non-imported.
Or people can just not declare static nested extensions.
(Or we could lean into it, and say that an extension declared inside Foo is always considered available for extension
member invocation on a Foo subtype. It's a way to make "sticky extensions". But then you should probably just declare the member on Foo directly.)

If that just works, is there something we'd want to add to that?

We could add a namespace declaration. Instead of using abstract final class FooTools { ...only static members...} to introduce a nested namespace for utility functions (even if it is against the style guide), we could allow namespace foo_tools { ... members ... }. You can omit the static there, like on top-level declarations. And I'd use snake_case names. just like import prefixes, to make it clear that this is only a namespace.

So far, nesting is still entirely lexical. You cannot include something into a namespace without actually writing it there.
You cannot nest deeper than what you're willing to write in a single file.
Because of that, I don't think we need a way to un-nest, to "open" a namespace so you can access its members un-prefixed.

But if we did, what would it be.

Consider a declaration of static import Foo hide bar; which would take all the static/namespace declarations of Foo and import them into the current lexical scope (except bar).
It could work just like library imports do in the global scope: Introduce (accessible) declarations into an import scope. Conflicts accepted if you don't use the name. Local declarations shadow imports. Can show/hide names.
It's like libraries are just big namespaces.

If we think of it that way, do we want export, part and import ... as declarations in namespaces too?

They would probably work, except that part is useless. You can just use import and export - but I guess part could be short for import-and-export, and it's fine to part the same scope in multiple places). Since it's only about static members, you won't get instance members of a class that way. (Using a part-like declaration for instance members is another feature, "partial classes", or maybe just mixin application.)

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 Foo.Bar.Baz.Bar.Baz to your hearth's content, but it's all statically resolved, so you're just accessing the same thing through extra symbolic links. But

class Foo {
  static import Foo;
  static int x;
}

could probably be disallowed. But also not really an issue, because the local x shadows the imported x, even if it is itself.
Exports could be more complicated.

class Foo {
  static export Foo show x;
  static int x;
}

Here the names coincide between the exported Foo.x and the declared x, but they refer to the same declaration, so we usually wouldn't consider it a value conflict.
It means we'll need a fixed-point calculation to figure out the actual export scopes of mutually recursive namespaces,
but we do that for libraries already, so it's probably completely doable. In fact, it'll likely be the exact same algorithm.

Do we allow a top-level export Foo;?
If we do, then the namespace and library export scope resolution becomes intertwined.
Again, probably doable, but getting more complicated.

TL;DR: Basic level of static declarations is essentially trivial, with only extension imports as something worth considering.
We can extend with pure namespace declarations, and potentially with import/export operators to reopen scopes into other scopes. That gets more complicated.

For the simple feature, my biggest worry is opportunity cost and complexity for readers.
Just because you can nest, it doesn't mean your should.

(Just allowing nested typedefs would solve a lot of the same issues without actually nesting classes, but would still effectively introduce nested namespaces because you can access static members through a type alias.)

@samandmoore
Copy link

(I'd also require the static for nested declarations, just in case we want to, one day, allow non-static nested declarations.)

This seems good to me. No objections 😄

If that just works, is there something we'd want to add to that?

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:

Consider a declaration of static import Foo hide bar; which would take all the static/namespace declarations of Foo and import them into the current lexical scope (except bar).
It could work just like library imports do in the global scope: Introduce (accessible) declarations into an import scope. Conflicts accepted if you don't use the name. Local declarations shadow imports. Can show/hide names.
It's like libraries are just big namespaces.

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 Widgets that consume that query i'd likely just want to import all the classes without the namespacing because I know that within this specific set of widgets i'm always working with the classes within the query's namespace.

TL;DR: Basic level of static declarations is essentially trivial, with only extension imports as something worth considering.

Good to hear.

We can extend with pure namespace declarations, and potentially with import/export operators to reopen scopes into other scopes. That gets more complicated.

As said above, this would seem very useful to my example use-case.

For the simple feature, my biggest worry is opportunity cost and complexity for readers.
Just because you can nest, it doesn't mean your should.

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.

(Just allowing nested typedefs would solve a lot of the same issues without actually nesting classes, but would still effectively introduce nested namespaces because you can access static members through a type alias.)

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.

@TekExplorer
Copy link

TekExplorer commented Nov 8, 2023

this... seems unecessary? you can already just define factory or static methods
or otherwise just...

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 Outer.Inner feels against dart style, as Capitalized Names are never meant to be inside another thing.

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 OuterInner instead. perhaps a better proposal is static typedefs?

class Outer {
  static typedef Inner = _OuterInner; // Outer.Inner();
}

// only accessable via Outer.Inner
class _OuterInner {}

though the inner PascalCase bugs me and messes with Dart style...

@TekExplorer
Copy link

TekExplorer commented Nov 8, 2023

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.

@lrhn
Copy link
Member

lrhn commented Nov 8, 2023

The Outer/Inner example doesn't work as a constructor unless Inner implements Outer. In neither case can you nest the actual type.

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 MapEntry class to the class it really belongs to.

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.
Might as well allow the nested types declaration directly then. The other thing that cannot do is nested extensions, since they are not types, and therefore can't be accessed though a type alias.

@TekExplorer
Copy link

The Outer/Inner example doesn't work as a constructor unless Inner implements Outer. In neither case can you nest the actual type.

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 MapEntry class to the class it really belongs to.

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. Might as well allow the nested types declaration directly then. The other thing that cannot do is nested extensions, since they are not types, and therefore can't be accessed though a type 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 MapEntry might be intended for use in a Map, but there's nothing stopping you from using it yourself for other purposes, even as just an old class-style tuple, or Map manipulation

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

@TekExplorer
Copy link

I also dont understand "nested extensions"
your claim that "Might as well allow the nested types declaration directly then." doesnt make much sense either as thats not reason enough to drastically make readability worse, and completely change up how classes are accessed and defined

this isnt a simple change, and i can easily see code generators completely breaking on these.

@lukehutch
Copy link

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

Is being able to define a function inside a function a recipe for bad code?

And the thing is, a MapEntry might be intended for use in a Map, but there's nothing stopping you from using it yourself for other purposes, even as just an old class-style tuple, or Map manipulation

Correct -- in Java you use an implementation SimpleEntry<K,V> extends Entry<K,V> all the time as a lightweight tuple solution for a language that does not have tuple support... but that's a bit of a hack, and conceptually it belongs to Map<K,V>. In this case, Entry probably does not need to be nested though.

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.

@TekExplorer
Copy link

Is being able to define a function inside a function a recipe for bad code?

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.

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.

Classes that only exist in a certain file already exists in _ClassName, which also takes care of the namespace issue.
If you want a public class that isn't present in the global namespace, then a static typedef on a private class makes more sense.

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

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.

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 this as a parameter and work with it through that.

I just don't see a strong enough usecase here that static typedefs wouldn't cover
also, nested classes would make figuring out what code is in which class aggravating in the extreme. a single misplaced bracket and suddenly a field you thought you defined turns out to not be externally accessible, and you may not even notice.
Retaining that clarity i think is way more important. Let scoping be a one-liner.

@feduke-nukem
Copy link

What is the status of this?

@josuerocha
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests