I have several times wanted to match on a value and check if it is an enum companion so that I know that it has e.g. the values member and can make use of it.
If the enum companion would extend a trait that let me match on the Color object I could get to the values if my x: Any is an enum companion.
Is there any downside in making the desugaring of an enum into trait+companion with the companion object extending a useful trait?
Or could it extend something that makes it possible to detect at runtime if a value is an enum object?
Or is there any other existing solution on how to match on a Color: Any object to see if it is indeed an enum object at runtime and get hold of itâs values member?
is this something that you are experimenting with, like in a REPL session, or this for a utility function that loads the values of an enum?
for the latter you can try enum-extensions library:
scala-cli --dep io.github.bishabosha::enum-extensions:0.1.1
Welcome to Scala 3.3.1 (21.0.1, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> import enumextensions.EnumMirror
scala> inline given AnyEnumMirror[E <: reflect.Enum]: EnumMirror[E] = EnumMirror.derived
def AnyEnumMirror[E <: reflect.Enum]: enumextensions.EnumMirror[E]
scala> inline def enumValues[E]: IArray[E] = compiletime.summonFrom {
| case m: EnumMirror[E] => m.values
| case c: reflect.ClassTag[E] => IArray.empty
| }
def enumValues[E]: IArray[E]
scala> enum Foo:
| case A, B, C
| enum Bar:
| case X(x: Int)
|
// defined class Foo
// defined class Bar
scala> enumValues[Foo]
val res0: IArray[Foo] = Array(A, B, C)
scala> enumValues[Bar]
val res1: IArray[Bar] = Array()
this way is still generic, rather than doing a runtime type check on a specific companion object
One use case is that I have a DSL of several enums and I want to pass any enum object to a function that iterates over their values.
this way is still generic, rather than doing a runtime type check
So I wonder if it would not be even simpler if enum desugaring made the enum object extend trait EnumObj { def values: Array[Enum] } or similar so that I can simply:
def iterate(x: EnumObj, f: Any => Result): Seq[Result] =
x.values.map(f).toSeq
it should be possible - we already add scala.deriving.Mirror.Sum, adding a new superclass/interface is supposed to be backwards compatible.
The problem is that now you will only get this on classes compiled since 3.5 or whatever, so not useful for libraries that may only be compiled once in the 3.0.0 days
sealed abstract class E ... extends <parents> with scala.reflect.Enum {
import E.{ <caseIds> }
<defs>
}
object E { <cases> }
And this Pre-SIP proposes that it is changed to something like:
sealed abstract class E ... extends <parents> with scala.reflect.Enum {
import E.{ <caseIds> }
<defs>
}
object E extends EnumCompanion { <cases> }
Where EnumCompanion looks something like:
trait EnumCompanion {
def values: Array[E]
def valueOf(name: String): E
def fromOrdinal(ordinal: Int): E
}
I think perhaps EnumCompanion has less of a risk of being confused with the actual cases (which are also objects). Here are candidate names that I have brainstormed:
Usability improvements for the most basic language features are always highly welcome.
Regarding the âuseful traitâ: How about making âEnumObjectsâ extend some EnumSet, a new collection type?
Enums are sets in the end. Just of some kind of specially treated values.
I would like to iterate, map, filter, etc. enum value sets. Also there could be overloaded applys for String and Int which return the matching EnumValue (typed more concretely of course). Would be cool to have this methods on the âEnumObjectsâ.
Iâm not sure, but maybe itâs even quite simple to add an EnumSet type as there is already a concept of EnumValue in the compiler. Itâs just synthetic at the moment. So EnumSet could be internally just a Set[EnumValue] if EnumValue would become a âreal thingâ (not only synthesised).
trait EnumCompanion[E] {
def values: Array[E]
def valueOf(name: String): E
def fromOrdinal(ordinal: Int): E
}
That way you can parameterize methods that need it like def foo[E](companion: EnumCompanion[E]): ???.
It might also be worth making EnumCompanion provide itself implicitly, so this could be done (which is done by scalapb and is really handy) def foo[E: EnumCompanion]: ???
the companion wonât extend anything useful no, and If you have some generic algorithm there isnât a simple way to get the companion object of an arbitrary class where you donât know its static type
@soronpo Thanks for the pointer. Would my simpler proposal above cover (large enough parts of) the use cases you envisioned in the previous discussions?