Proposal for Enumerations in Scala

enumish :slight_smile:

I like this approach. It both gives a nice sugar to define simple adts and also gives a way to tell the compiler that we expect this adt to be enumerable. coproduct as keyword is nice, adt might also work.

I know that might-be-an-enum is tongue-in-cheek, but more precise name would be he-compiler-I-expect-this-to-be-enumerable-please-error-if-otherwise.

I think this was not suggested yet:

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

This is a very good point! I remember Simon Peyton Jones (of Haskell fame) saying that what has kept things sane when adding more and more features to the language is that everything had to compile down to an intermediary language Core (basically System Fω IIRC) which was stable. That limited the field of what crazy features could get in and which couldn’t. And we now have, such an intermediary language for Scala, TASTY, don’t we? :wink:

But I definitely won’t push on splitting the concepts, it’s probably for the best, to have enumerations as a special case of more general ADTs. Perhaps it’s only the keyword that was confusing, enum suggests assigning natural numbers to cases, which we don’t do in the general ADT cases.

How about using tagged union? This would communicate well the similarity and difference from union types of Scala 3. (Other option would be variant; coproduct, while quite correct, seems to me cumbersome to say.)
@enum could then be used as an check that the data is flat and ensure that the Enumeratum-like features are generated, kind of like @tailrec, as has been proposed before in the thread.

@enum
tagged union Fruit(sugarContent: Double) {
  case Apple(0.5)
  case Orange(1.25)
}
Fruit.Apple.value == 0
Fruit.Orange.sugarContent == 1.25
Fruit.valuesToEntriesMap(0).sugarContent == 0.5
Fruit.values == List(Fruit.Apple, Fruit.Orange)
Fruit.Apple: Fruit

tagged union Expr {
  case Zero
  case Val(v: Int)
  case Sum(l: Expr, r: Expr)
}
Expr.Val(42): Expr // the fact, that `Val` is implemented with a `class` `Val` is a detail, that should be _hidden_

On the other hand, I still don’t think nesting tagged unions or exposing the classes of the cases would be of much value. For solving general purposes like that, there are already the general purpose sealed hierarchies.

2 Likes

Or, alternative way to introduce enumerations, would be as type classes, something like

// trait Enum[F] extends Ord[F]

tagged union Fruit(sugarContent: Double) derives Enum {
  case Apple(0.5)
  case Orange(1.25)
}
val fruitEnum = summon[Enum[Fruit]]
fruitEnum.value(Fruit.Apple) == 0
Fruit.Orange.sugarContent == 1.25
fruitEnum.valuesToEntriesMap(0).sugarContent == 0.5
fruitEnum.values == List(Fruit.Apple, Fruit.Orange)
Fruit.Apple: Fruit
1 Like

Alternatively

sealed trait Reference

sealed trait ResolvedReference extends Reference

enum BuildReference extends Reference {
  case BuildRef(build: String) extends BuildReference with ResolvedReference
  case ThisBuild
}

enum ProjectReference extends Reference {
  case ProjectRef(build: String, project: String) extends ProjectReference with ResolvedReference
  case LocalProject(project: String)
  case RootProject(build: String)
  case LocalRootProject
  case ThisProject
}
scala> val tb = BuildReference.ThisBuild
val tb: BuildReference = ThisBuild

scala> val br = BuildReference.BuildRef("/d/foo")
val br: BuildReference = BuildRef(/d/foo)

scala> ProjectReference.ProjectRef("/d/foo", "fooRoot")
val res0: ProjectReference = ProjectRef(/d/foo,fooRoot)

scala> ProjectReference.LocalProject("fooRoot")
val res1: ProjectReference = LocalProject(fooRoot)

scala> ProjectReference.RootProject("/d/foo")
val res2: ProjectReference = RootProject(/d/foo)

scala> ProjectReference.LocalRootProject
val res3: ProjectReference = LocalRootProject

scala> ProjectReference.ThisProject
val res4: ProjectReference = ThisProject