Skip to content

Dart enum toString is too verbose #30021

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
jacob314 opened this issue Jun 26, 2017 · 24 comments
Closed

Dart enum toString is too verbose #30021

jacob314 opened this issue Jun 26, 2017 · 24 comments
Labels
area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). closed-duplicate Closed in favor of an existing report core-m customer-flutter type-enhancement A request for a change that isn't a bug

Comments

@jacob314
Copy link
Member

Flutter can't use the default toString() for enum types as it is too verbose and instead has to carefully call a describeEnum(entry) helper whenever an enum entry is displayed in a message.
The describeEnum helper strips out the enum type from the result of calling entry.toString().
It would be nice if the default toString() for enum just displayed the enum value or if the enum class provided a toStringShort() method that returned just the enum value.

Note that you someone who really wanted to display Type.entry could just write

'${entry.runtimeType}.${entry}'

Example of current behavior:

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

main() {
  assert(Day.monday.toString() == 'Day.monday');
  assert(describeEnum(Day.monday) == 'monday');
}
@lrhn
Copy link
Member

lrhn commented Jun 27, 2017

Using "${entry.runtimeType}.$entry" would be assuming the behavior of Type.toString which is not specified anywhere.
I would really, really like if we could get away from having toString that somehow reflects on source names, like the toString of enums, Type and Object. It's nice for debugging, but it does require retaining source names at runtime, and that's a can of worms when people start using it.

As for enums, the Dart enum design is very plain, enum instances are not carrying any value except their distinguishing index, and that includes a string of their source name. Their toString is meant to make them distinguishable when debugging, also from other enum types that may share an element name, and not to somehow be a reflection of their name.

It seems that you actually do want a string that matches their declared name, and even as a string, not a symbol. That's two steps removed from the actual declaration, and not something that is generally desirable.

That said, we did chose to provide the name in the toString so it's annoying that there is no other way to get it. We could add a name getter to enum instances, but that would preclude using name as an enum element name – no enum AddressPart { name, address, zip, city, country; } enums allowed – so it's a breaking change.
Co-opting the toString is as breaking, because people (like you) are already using it.

@floitschG floitschG added the area-language Dart language related items (some items might be better tracked at github.com/dart-lang/language). label Jun 27, 2017
@jacob314
Copy link
Member Author

To clarify, Flutter uses the short name as part of debugging messages so symbolizing names in production would probably be acceptable. The debugging messages can get very large and need to be easy to read so showing the class name as well as the entry name doesn't work.

I agree that adding a name getter is bad for the reasons you suggest.

To avoid a breaking change we could add a top level method to the core library that gets the name of an enum entry. E.g. enumEntryName(Object o)

Using a more verbose name would also be reasonable. We can track the number of occurrences of the verbose name in pub packages to verify the impact of the breaking change will be small. For example, I would guess than toStringShort is not used as an enum entry in any existing package.

@rakudrama
Copy link
Member

rakudrama commented Jun 27, 2017 via email

@lrhn
Copy link
Member

lrhn commented Jun 28, 2017

We don't make it easy to recognize enum values as being so. There is no common type that enum classes evaluate to (unlike, say, Java where enum Foo implements Enum<Foo>).
If we did that, it's likely that Error.safeToString or similar methods could handle enums as well without needing a platform hook to recognize it as safe.

@jacob314
Copy link
Member Author

jacob314 commented Jun 28, 2017 via email

@rakudrama
Copy link
Member

What are the benefits of the type parameter in enum Foo implements/extends Enum<Foo> ?
There a cost to the type parameter (in dart2js the 'implements' clause is stored as metadata on the class).

@lrhn
Copy link
Member

lrhn commented Oct 23, 2017

Looking at the Java implementation, the only two methods on Enum that uses the type parameter are:

int compareTo(E o)
Class<E> getDeclaringClass()

We don't need the getDeclaringClass and we haven't made enums comparable, so the type parameter doesn't currently give us a lot of benefit.

Another use-case of generic classes in general is to restrict to a sub-hierarchy (withWidgets(List<Widget> ...)) but since you can't subclass enums, the sub-hierarchy Enum<Foo> is equivalent to just Foo, so no advantage there.

We could just make enum a non-generic class that declares int get index.

So, unless we intend to add methods to Enum or allow non-trivial sub-hierarchies, I don't see any advantage in making it generic. It's needed only in cases where we want to treat multiple enum types in a generic manner, and where calling a method on the enum value itself should return or accept something of its own type. We don't have that problem.
If we wanted to make the enum entries comparable, we could do:

bool operator <(covariant Enum other);  // in Enum

and override it in each implementation (or cheat, since we probably don't allow anybody to implement Enum manually, so we can use the internal structure of the enum).

@alan-knight
Copy link
Contributor

This also comes up with Intl. There's a select method that lets you choose from several alternatives. It's a natural fit for enums, but it's defined to accept only certain characters in select identifiers, and excludes ".". We could transform the result of the enum's toString(), but that gets pretty ugly.

@lrhn lrhn added the type-enhancement A request for a change that isn't a bug label Jun 25, 2018
@codegrue
Copy link

codegrue commented Jul 6, 2018

Here is another way to do the conversion:

Day.monday.toString().split('.')[1];

@lrhn lrhn added the core-m label Dec 6, 2018
@lrhn
Copy link
Member

lrhn commented Dec 6, 2018

This is really asking for a way to get just the name of the enum, so it's a duplicate of dart-lang/language#1511.

@lrhn lrhn closed this as completed Dec 6, 2018
@lrhn lrhn added the closed-duplicate Closed in favor of an existing report label Dec 6, 2018
@rknell
Copy link
Contributor

rknell commented May 24, 2019

I just made a package for this... https://pub.dev/packages/enum_to_string

Pretty simple and welcome and pull requests!

@techfan101
Copy link

Late to the party, but I thought I'd share this solution in case it helps others.

I settled for defining a "toText" extension method to accompany my enum. This allowed me to perform whatever logic I wished a "toString" override might perform. In my case, I wanted to exclude the type specifier and translate underscores to spaces, so it looked like the following:

extension MyEnumEx on MyEnum {
  String toText() =>
    toString().split('.')[1].replaceAll('_', ' ');
}

This allowed for:

String text = MyEnum.SomeEnum.toText();

FWIW, worked great for me.

@LukaGiorgadze
Copy link

I solved enum to string conversion like this:

import 'package:flutter/foundation.dart';

enum MyEnum { ONE, TWO }

extension MyEnumExt on MyEnum {
  String get toStr => describeEnum(this);
}
// usage:
print(MyEnum.ONE.toStr); // prints 'ONE'

🎉

@esDotDev
Copy link

esDotDev commented Apr 5, 2021

It's really unfortunate we have to manually write this code for every enum we declare, and what about fromString? That is the much more verbose one.

Even using a package, I have to write:

MyEnum t = EnumToString.fromString(MyEnum.values, value ?? "") ?? MyEnum.TabA

Ideally it should just be like this:

String s = MyEnum.foo.toString()
MyEnum t = MyEnum.fromString(value, default: MyEnum.TabA)

I'm surprised this is closed with such a cludgy workaround for such a core feature of the language.

Why make developers pass around .value when the compiler already knows it, or write the exact same get toStr => describeEnum(this); over and over again?

@bernaferrari
Copy link
Contributor

Can we have this native now with enum having a supertype in Dart 2.14?

43cff26

@lrhn
Copy link
Member

lrhn commented Jun 28, 2021

I'm still philosophically opposed to exposing source names at runtime.
I can also see that everybody wants it, and we are exposing it already (and badly so).

(I wish someone would have [written a warning] about exposing properties of an object only in its toString, and how people would just parse the string to get it,)

@jolleekin
Copy link

@lrhn How do you serialize/deserialize enums without knowing their names?

@lrhn
Copy link
Member

lrhn commented Jun 28, 2021

@jolleekin Lots of ways are possible. You can use the index, which uniquely defines the value in the EnumName.values list. You can use your own constant strings.
Deserializing using a name is actually harder than using the index because there is no built-in mapping from string to enum value.

If you intend to interact with other languages, you need a shared dictionary anyway. The word used there doesn't need to be the Dart name. If the serialization key is foo_bar, you should still name your Dart enum value fooBar. You'll need the conversion on the side.

@bernaferrari
Copy link
Contributor

bernaferrari commented Jun 28, 2021

I think you should search for how many open source dart projects have describeEnum to see if this is really popular/necessary. I think so, but maybe no one ever uses it...

@codegrue
Copy link

codegrue commented Jun 28, 2021

I disagree with the whole index serialization philosophy, Counterpoint: the position of the enum in the list should be irrelevant, the "name" (i.e. content or intent) is what makes it meaningful.

enum myEnum {Yes, No, Maybe} from the sender should be compatible with enum myEnum {Maybe, Yes, No} by the recipient when it's hydrated on the other end. At some point, in between, like it or not, the enums will be represented as strings, so the language should be able to handle this, or at least make it easy to do so.

I guess I'm arguing for a myEnum.fromString(string value) type function, which isn't the point of this thread I just realized.

@esDotDev
Copy link

esDotDev commented Jun 28, 2021

I think the real truth is that both approaches are valid. There are times where serializing by index is more robust or well suited to your data, other times you want them stored as values for whatever reason.

C# allows both, and developers use both, depending on circumstances. There's no point arguing whether an orange tastes better than an apple.

Generally speaking, serializing by value works better when you have a very large set of enums, that are constantly gaining new entries, (think collectable items in an a game). I can't have serialization breaking each time I add an item, and because of the huge set of items, I want to keep things alphabetically sorted, not just tack them on to the end of the enum in random order. So names is much nicer to work with here, because order is irrelevant.

In another case, where I have fewer items, I don't care as much about alphabetical sorting, and I want to instead prioritize the ability to easily rename any of the items to enhance readability in my codebase. Then index based works better because value is irrelevant.

In either case serializing enums is brittle, and it always will be. But the developer is in the best place to decide which is least brittle for their dataset.

@lrhn
Copy link
Member

lrhn commented Jun 29, 2021

Serialization means a lot of things. Here we're talking about storage of the serialized data which spans multiple program versions, all written in Dart. That's a finnicky thing to go for, the result might not make sense at all if you end up removing elements from the enum. You need to properly version your storage format. Using a string to represent the enum value is perfectly fine, but it doesn't have to be the source name from the enum declaration.

Other serialization options are between different instances of the same version of the program (any consistent serialization works), or between completely different programs which may not even be written in the same language (you need a completely specified communication protocol down to the byte level).

For enum myEnum {Yes, No, Maybe}, that's showing why I don't want to use source names for anything. To follow Dart programming style, that should be enum myEnum {yes, no, maybe} (lower-camel-case constants). If the communication protocol calls for "Yes", "No" and "Maybe" as representations, getting access to the Dart source name won't help you.
And that's not considering enums where you'd use snake_case or SCREAMING_CAPS as the representation.

You really want something like Java's enum declarations:

enum MyEnum {
  yes("Yes"),
  no("No"),
  maybe("Maybe");
  final String serializationID;
  MyEnum(this.serializationID);
}

Focusing on access to the source name as a string is a red herring, when we should be focusing on how to store more information on the enum instead. That'd work with anything, and would not depend on coding style for the enum constant names.

@esDotDev
Copy link

esDotDev commented Jun 29, 2021

I think when you get into advanced scenarios like that, enums are not really worth their weight. It's better to just use strings, or ints, and KISS by using a primitive that easily crosses language boundaries.

The main issue here that needs addressing imo, is just basic serialization of enums in dart, which is overly cludgy. describeEnum does work ok so maybe that's all we need. Not the cleanest API design, but hey, it works.

@bernaferrari
Copy link
Contributor

bernaferrari commented Oct 1, 2021

Starting on Dart 2.15 now it can. There is enum.name now!

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). closed-duplicate Closed in favor of an existing report core-m customer-flutter type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests