When overriding toStringin an enum, simple cases end up using their name instead of this new implementation.
enum E:
case A
override def toString: String = "overriden"
println(E.A)
overriden
A
Note that adding an argument to that enum case will return the right result again:
enum E:
case A(arg: Unit)
override def toString: String = "overriden"
println(E.A)
This prints overriden as expected.
This has something to do with the desugaring of enums, and the generation of a default toString method.
This might be working as intended (which doesn't mean it isn't an issue...).
You example is overriding the toString method from trait E. As explain in the enum spec, parametrized enum desugar to case classes while parameterless enums desugar to their own custom thing. Case classes get a default toString method unless they already have/inherit a toString method other than the one from Object. On the other hand, the "custom thing" that parameterless enums desugar to unconditionally override toString.
Yeah it does make sense considering how the implementation is.
I do think it does make it a somewhat leaky abstraction, because I need to reason about how the implementation of enum happens to work in order to understand things like this.
I do think it does make it a somewhat leaky abstraction, because I need to reason about how the implementation of enum happens to work in order to understand things like this.
Totally agree, I think having enum specified as a desugaring step is not really ideal...
Ideal or not, that's how enums are currently specified. So far there is a better alternative.
Could we fix this issue by desugaring parameter-less enums to case objects instead of vals?
I don't think it is about vals vs case objects. Translation specifies that a case of an enum is an instance of an anonymous class extending the parent enum and overriding its toString method. The same can be done via an object but to change the semantics we need to change the spec of what an enum case is translated to.
Yes. vals and objects behave the same here. It makes no difference wether I write case A (which is implemented by a val) or case A extends E (which is implemented by an object). But for consistency it would be good if toString was not affected by whether a case is parameterized or not. Right now we get in
enum E {
case A
case B()
override def toString: String = "overriden"
println(E.A) // --> A
println(E.B()) // --> overridden
It would be good if we could change everything to overridden. This means we need a separate method to retrieve the name of an enum value; toString will not work in this role anymore.
So, to fix this, we need a redesign of (this aspect of) enums, and an implementation.
We should reconsider adding name methods to Scala enums
@anatoliykmetyuk Indeed. And make the toString final and return the value of name?
my current idea is since scala.Enum extends Product we can just use productPrefix instead of adding name
my current idea is since scala.Enum extends Product we can just use productPrefix instead of adding name
I don't think that's a good idea. While reasonable from DRY and engineering perspective, it adds a mental load on the programmer's mind. Now, to properly use enums, we need to keep in mind that they are products. For new programmers, this can be an extra learning curve when learning enums.
@anatoliykmetyuk Thanks for your advice, I've said in this comment why at least the name method is difficult to add:
https://github.com/lampepfl/dotty/pull/9549#issuecomment-674000889
as per https://github.com/lampepfl/dotty/pull/9549#issuecomment-674033755 I can add an enumLabel method which should hopefully be simple to remember and not clash with any fields in class cases
I like enumLabel, I think that's a good name. Thanks for implementing it!