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

More concise syntax for defining sealed families #3021

Open
mraleph opened this issue Apr 27, 2023 · 9 comments
Open

More concise syntax for defining sealed families #3021

mraleph opened this issue Apr 27, 2023 · 9 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@mraleph
Copy link
Member

mraleph commented Apr 27, 2023

With sealed class families and pattern matching developers are understandably excited to use them for data modelling in their programs. However the syntax is overly verbose for simple families, e.g. modelling success / failure in the API requires writing something like this:

sealed class Result<T> {
  const Result();
}

class Ok<T> extends Result<T> {
   final T value;
   const Ok<T>(this.value);
}

class Error extends Result<Never> {
  final String message;
  const Error(this.message);
} 

With primary constructors this is reduced to

sealed class Result<T> {
  const Result();
}

class Ok<T>(final T value) extends Result<T>;
class Error(final String message) extends Result<Never>;

Which is much better but still has some repeated boilerplate here: repeated extends Result<*>.

I suggest expanding upon primary constructors and introduce a special syntactic sugar for simple sealed families like this

sealed class Result<T> {
  case Ok<T>(final T value);
  case Error(final String message);
} 

Here case C<T1, ..., Tn>(v0, ..., vN) { m1 ... mN }? occurring inside sealed class B<U1, ..., Un> gives raise to

class C<T1, ..., Tn>(v0, ..., vN) extends B<Forward({Ti}, {Ui})> {
  // Optional methods
  m1
  // ...
  mN
}

Forward(...) is defined as forwarding type parameters based on their names (or maybe indices - I am not sure what is better). If Ui does not have a corresponding Ti then Never is passed instead.

/cc @munificent @lrhn

@mraleph mraleph added the feature Proposed language feature that solves one or more problems label Apr 27, 2023
@rrousselGit
Copy link

Why the "case" keyword instead of "class"?

@mraleph
Copy link
Member Author

mraleph commented Apr 27, 2023

@rrousselGit it makes a clearer connection to the surrounding class. class within a class does not convey the same meaning. case makes it clearer.

@mraleph
Copy link
Member Author

mraleph commented Apr 27, 2023

Talk about timing: @eernstg has just uploaded primary-constructors proposal (which I knew was cooking, but could not find anywhere to refer to).

I have now reworded original proposal to simply delegate to primary-constructors syntax.

@rubenferreira97
Copy link

rubenferreira97 commented Apr 27, 2023

Like switch removed case:

switch(aInt) {
 1: print('1'),
 _: print('not 1'),
}

Couldn't we just omit case like:

sealed class Result<T> {
  Ok<T>(final T value);
  Error(final String message);
} 

or that collides with current grammar? I think it's fine to assume that a class declaration inside a sealed class is a case.

@mraleph
Copy link
Member Author

mraleph commented Apr 27, 2023

@rubenferreira97 the syntax you propose is valid today and defines two abstract instance methods.

@rrousselGit
Copy link

@rrousselGit it makes a clearer connection to the surrounding class. class within a class does not convey the same meaning. case makes it clearer.

Would we be able to use all the class syntaxes though? Like mixins, abstract/final/sealed/internal, implements/extends, ...?

If so, I'd prefer using "class". I think the nesting would be enough to communicate the relationship.

@rubenferreira97
Copy link

rubenferreira97 commented Apr 27, 2023

Just to add that in the future possibility of Dart adding data classes, the syntax case data would be strange. Kinda the same problem with modifiers that @rrousselGit described.

sealed class Result<T> {
  data class Ok<T>(final T value);
  data class Error(final String message);
} 

@rrousselGit
Copy link

Also what about two-level nesting?

Like:

sealed class A {
  sealed class B {
   class C {}
   class C2 {}
  }
  class B2{}
}

@munificent
Copy link
Member

munificent commented May 1, 2023

I'm definitely interested in some better syntax in this area, yes. The main challenges that I know of are:

  • Users have also requested static nested classes (Static nested classes #336). I think that's also a useful feature, so I'd like to make sure whatever syntax we come up with doesn't collide with that.

  • As @rrousselGit suggests, it would be nice if this extended to support entire sealed hierarchies and not just a single level deep.

  • The subtypes of a sealed supertype might implement or extend it and it would be nice (but not strictly essential) to support being able to express both of those.

I think those are likely tractable, but it will take some work to sort out.

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

4 participants