Skip to content

Add a parse static or factory ctor to Enum #33244

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

Closed
kevmoo opened this issue May 26, 2018 · 19 comments
Closed

Add a parse static or factory ctor to Enum #33244

kevmoo opened this issue May 26, 2018 · 19 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). core-n type-enhancement A request for a change that isn't a bug

Comments

@kevmoo
Copy link
Member

kevmoo commented May 26, 2018

C# has it: https://docs.microsoft.com/en-us/dotnet/api/system.enum.parse?view=netframework-4.7.2

The opposite of #30021 & https://github.com/dart-lang/sdk/issues/21712 – where we want the simple string name of the value.

Related to #21966 as this makes interop VERY hard. Have to do a lot of messy string tricks

@kevmoo kevmoo added area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). type-enhancement A request for a change that isn't a bug labels May 26, 2018
@leafpetersen
Copy link
Member

leafpetersen commented May 26, 2018

Given:

enum Day {
  monday, tuesday, wednesday, thursday, friday, saturday, sunday
}

Is it enough to be able to do Day.parse("monday")? Or do you want to be able to say Enum.parse(Day, "monday")? Note that the latter is strictly more powerful, since I can presumably do

class A<T> {
  var a = Enum.parse(T, "hello"); // Better hope T is an enum type with a "hello" tag
}

and

dynamic mkHello<T>() => Enum.Parse(T, "hello");

@kevmoo
Copy link
Member Author

kevmoo commented May 26, 2018 via email

@natebosch natebosch changed the title Add a parse static or factor ctor to Enum Add a parse static or factory ctor to Enum May 26, 2018
@lrhn
Copy link
Member

lrhn commented May 26, 2018

I'm not very happy about conflating source names and strings.
On the other hand, the toString already does so, so we have to retain string representations of the source name anyway.

Adding members to enum classes takes up namespace for values, so adding a parse method would prevent having a parse enum entry. I guess tryParse is obscure enough that it's not going to conflict.

The ignoreCase parameter makes sense now, because names are ASCII only, but if we ever allow source names to be arbitrary Unicode identifiers, I'd be very worried about having to do case-insensitive text comparison here.

All in all, I think we should do a proper work-over on enums rather than add features piecemeally.

@lrhn
Copy link
Member

lrhn commented Jun 22, 2018

One, non-breaking, option is to change the values field to be a class that still implements List, but which also has a byName function (or even a byName map). Then you write:

Day.values.byName("monday") // if `byName` is a Day Function(String)
// or
Day.values.byName["monday"]  // if `byName` is a Map<String,Day>.

I would only accept the name that occurs in toString, no case insensitivity. Whether to accept other representations should be purely client discretion, not something we try to speculate on.

(I'd prefer to change toString to not include the type prefix, it's annoying to have to split the result to get the name, and I also don't want to add a name getter on enums, since it conflicts with the enum declarations. It's a breaking change, though).

@lrhn lrhn added the core-n label Dec 6, 2018
@MatrixDev
Copy link

I would also suggest to allow developers to write custom parse methods inside enum itself:

enum Day {
  monday, tuesday, wednesday, thursday, friday, saturday, sunday;

  static Day parseDb(String dbValue) => ...
  static Day parseJson(String jsonValue) => ...
}

@lrhn
Copy link
Member

lrhn commented Jan 5, 2021

Allowing static members inside an enum declaration should be trivial. They'd have to not be named index or values, the two members that all enums have, or the same as any enum value, but otherwise there is no problem.

We might want to allow instance members too, and have something closer to, say, Java's enum declarations, but that's a much larger change.

@MatrixDev
Copy link

Having Java-like enums would really be perfect. But if static functions are more trivial - I'd say to add them first. It will significantly reduce global namespace pollution (aka parseDay -> Day.parse).

@Levi-Lesches
Copy link

Levi-Lesches commented Apr 8, 2021

I like the idea of adding static members to an enum. Then automatically adding something like static MyEnum parse(String)
shouldn't too difficult. Again, just like MyEnum.values, if we choose a name for this function/map that's people aren't using as enum values, it shouldn't be that big a deal.

@marcglasberg
Copy link

marcglasberg commented May 7, 2021

This is what works best while this is not implemented:

extension EnumExtension<T> on List<T> {

  T from(String value) => firstWhere(
        (e) => e.toString().split('.').last == value,
        orElse: () => throw AssertionError('Enum "$T.$value" does not exist.'),
      );

  T? fromOrNull(String? value) => (value == null) ? null : from(value);
}

Use it like this:

Day day = Day.values.fromOrNull('saturday');

But it would be better if we could write:

extension EnumExtension<T extends enum> on List<T> {

Why can't we write T extends enum?

@Levi-Lesches
Copy link

Because there currently is no such Enum class that all enums extend from

@bernaferrari
Copy link
Contributor

bernaferrari commented Jul 4, 2021

Now there is an enum class.

@Levi-Lesches
Copy link

Mind elaborating on that? I can't find anything on DartPad or the docs about it.

@bernaferrari
Copy link
Contributor

bernaferrari commented Jul 4, 2021

It is in 2.14.0, every enum extends/implements Enum class.
https://github.com/dart-lang/sdk/blob/master/CHANGELOG.md

@deakjahn
Copy link

And since 2.15, there is a:

SomeEnum.values.byName(s)

@eernstg
Copy link
Member

eernstg commented Jul 18, 2022

I believe that the per-enum support for this feature is covered quite well by SomeEnum.values.byName(s), as mentioned by @deakjahn here. We might want to have a byCaseInsensitiveName and other variants of byName, but that is implementable without any language enhancements.

However, as @leafpetersen mentioned here, the type-abstracting version of this feature is more powerful:

.. do you want to be able to say Enum.parse(Day, "monday")? .. [which] is strictly more powerful [because we can do] ..

dynamic mkHello<T>() => Enum.Parse(T, "hello");

Today we have enumerated types with type arguments, and we'd need to consider what it means when the value of T is MyEnum<num> — would we receive the object MyEnum.someValue in the case where it has the type MyEnum<int>? Probably. But probably not in the case where it has the type MyEnum<Object>, even though it would not be unsound for Enum.parse to return such an object. However, I think that we could sort out how to handle that question.

The most important remaining discussion in this issue would then be whether any such type-abstracting features would fit into the language.

Presumably, such features could only be implemented if the language were to introduce support for iterating over all relevant types (here: all enumerated types) in the current program. This could probably be done by generating some code at link time (whatever that means, for each platform and configuration). However, I suspect that this kind of feature could be unable to coexist peacefully with tree shaking.

Conversely, if we do overcome the difficulties with typing and tree shaking, why wouldn't we open up the language to support other type-abstracting features based on user-defined code? This might be handled via static meta-programming, if we support iterating over all those types (using some kind of query over all types in the current program) and generate a snippet of code for each of them.

But in that case we might also be able to support a function like create<T>() which would invoke a constructor of type T for each value of T which is a class that has a constructor that accepts the empty argument list, passing type arguments of T to the constructor invocation, and so on and so on. ;-)

My conclusion is that this issue could serve as a source of inspiration for some really interesting discussions about static meta-programming, and about the borderline between statically known types and run-time objects.

However, I also think that those topics are so different from the original scope of this issue that they should be handled in other issues.

@kevmoo, WDYT? Can we close this issue, and ask each contributor to the discussion to create new issues if they wish to explore any of those spin-off topics?

@kevmoo
Copy link
Member Author

kevmoo commented Jul 18, 2022

I'll live w/ the current implementation, but I'd argue discoverability is still lacking. It'd be great to have a parse or similar function on the type.

@kevmoo kevmoo closed this as completed Jul 18, 2022
@eernstg
Copy link
Member

eernstg commented Jul 18, 2022

discoverability is still lacking

Yes, it's not obvious. I don't think it's easy to come up with a general solution (perhaps a macro), but if we're willing to write just one line of code per enum then we can do this:

enum E {
  one,
  two;
  static final parse = values.byName;
}

void main() => print(E.parse('one'));

@kevmoo
Copy link
Member Author

kevmoo commented Jul 18, 2022 via email

@eernstg
Copy link
Member

eernstg commented Jul 19, 2022

Cf. dart-lang/language#2348.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). core-n type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

9 participants