enumish
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
}
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?
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 union
s 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.
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
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