Proposal for Enumerations in Scala

This is fantastically written. You’ve made me much better realize and acknowledge the motivation behind the proposal.

I think we still need to further finalize a solution to the ambiguity of values and valueOf, which depends on whether the ADT has only fixed values (case objects) or case classes as well. Perhaps these methods should not be included by default, but rather require an additional keyword:

iterable enum Color {
 case Green
 case Blue
}
Color.values()

And the compiler could emit an error when the keyword (probably not iterable but something else) is mixed with non-fixed cases:

iterable enum Option[+A] {
  case Some(value: A) // error
  case None
}

Edit: Maybe fixed? finite?

Whatever the properties of enum, the type inference for Option, Either, List and other standard library types will not change because they will remain sealed traits. This is an ad-hoc topic treatment at best, not a principled solution, and will not address the most common cases of bad inference that stem from standard library usage – some of which are seemingly already addressed, with the rest having a way to be improved as per Martin’s posts.

Kotlin has multi-leveled sealed hierarchies, Java will have them soon and most importantly Scala already has them. One niche language from 1990 may not have them, but this in no way relevant for Scala.

Perhaps it’s interesting to note that Kotlin went from sealed hierarchies with nested definitions to non-nested definitions. I’m not familiar with Kotlin, I gathered this from their documentation:

Before Kotlin 1.1, the rules were even more strict: classes had to be nested inside the declaration of the sealed class

1 Like

I think the Scala compiler could see whether or not it can generate these methods based purely on the existence of public constructors. I’m not sure an annotation would be required.

It’s true that values and valueOf do not make any sense in general for sum types; only in enumerable sum types.

That is true, but these cases are representative of a larger class of problems that occurs when users try to build data models using sealed traits and case classes.

What case class did for product types, I would like enum to do for sum types: provide a safe and sane choice for data modeling that the majority of developers can use without even thinking.

I’m not arguing against multi-level enums, I just think this feature can safely wait until post 3.0.

Of course. That keyword is not for “him” (in Hebrew a compiler is a he), but for the reader :slight_smile: Perhaps an (optional) annotation would suffice.

2 Likes

A bit like @tailrec maybe.

3 Likes

sealed was supposed to be that in the first place. I don’t think having separate sealed & enum is useful, one must supersede the other – and that’s why it’s important for enums to be feature complete.

Does anyone know how to find all the comments a particular user (in this case, me) made in the issue tracker of a project (in this case, dotty)? Because I have replied several times over the past year or two, to various unrelated issues, something along the lines of “this is another problem caused by the fact that enum constructors return the enum type instead of the particular case’s type”. But I can’t find them to list them here :frowning:

2 Likes

Ideally, it’d act like @tailrec as well: generating .values silently whenever it notices it’s possible, but only throwing an error if the annotation indicates the programmer expects .values is possible, but the compiler doesn’t agree.

enum seems somewhat inadequate as a term, in the sense that it doesn’t really describe all the use cases this construct appears intended to represent.

How about something like this (which borrows from ideas in a couple other posts I can’t find at the moment):

This would produce a union type, and be essentially equivalent to a sealed trait (this would not generate .values and .valueOf):

union class Foo {
  case Bar(a: Int)
  case Baz(b: String)
  case Qux
}

This would produce something equivalent to a Java Enum (this would generate .values and .valueOf):

union class Foo {
  case Bar
  case Baz
  case Qux
}

This would be a compilation error:

@enum
union class Foo {
  case Bar(a: Int)
  case Baz(b: String)
  case Qux
}
1 Like

(https://github.com/lampepfl/dotty/issues?utf8=✓&q=is%3Aissue+commenter%3Asjrd+enum+)

3 Likes

Well, definitely not class. If anything, union type or sum type, though both “union” and “sum” are fairly commonly used as identifiers / method names.

Fair enough.

I like union because it’s got a long history of being used for sum types in languages like C++, so it’ll be familiar.

I went with class to provide symmetry with case class, though I think you may be right that type would probably be a better fit.

It doesn’t seem like a great idea to overload “union” when we now have union types and then confuse people with “enum unions” / “union classes”. I think there’s nothing wrong with the term “enum”. It’s been used very happily by Rust users for pretty much the exact same thing, and Martin’s survey on the eve of scaladays 2019 showed that beginners overwhelmingly love the idea of enums.

4 Likes

I love the idea of enums, and I love the idea of an easier way to define an ADT, it’s the name given that idea that doesn’t seem to fit :slight_smile:

If you can’t enumerate the possible values, how does it make sense to call it an enumeration?

4 Likes

I mean, I guess you can say that you can enumerate all the variants of an enum.

1 Like

For whatever it’s worth, even Wikipedia calls out Rust for misusing the term:

Though Rust uses the enum keyword like C, it uses it to describe tagged unions, which enums can be considered a degenerate form of.

Perhaps a less commonly used term could be “coproduct”, which is less familiar, but better describes what this construct is actually doing. This would also free up “enum” to be used in an annotation for when you want to enforce the constraint that it be an actual enum:

coproduct FooADT {
  case Bar(a: Int)
  case Baz(b: String)
  case Qux
}

@enum coproduct FooEnum {
  case Bar
  case Baz
  case Qux
}
3 Likes

Rather than using enum, perhaps the keyword should be might-be-an-enum instead :wink:

But I agree, just because Rust has named their enum the wrong thing, doesn’t mean that Scala should follow suit, and I don’t really understand how it is okay to not be able to enumerate an enumeration.

If it really defines an adt, then perhaps the keyword should be adt

1 Like

Careful, that road leads to madness :wink::

@definitely-an-enum might-be-an-enum FooEnum {
  case Bar
  case Baz
  case Qux
}

Since it is mostly for matching, maybe we should call is a match class. That would not cost an extra keyword. :slight_smile: