I want to have a simple enumDescr
function for any Scala 3 enum.
Example:
@description(enumDescr(InvoiceCategory))
enum InvoiceCategory:
case `Travel Expenses`
case Misc
case `Software License Costs`
In Scala 2 this is simple (Enumeration
):
def enumDescr(enum: Enumeration): String =
s"$enum: ${enum.values.mkString(", ")}"
But how is it done in Scala 3:
def enumDescr(enumeration: ??) = ...
In Scala, there is no enum keyword unlike Java or C. Scala provides an Enumeration class which we can extend in order to create our enumerations. Every Enumeration constant represents an object of type Enumeration. Enumeration values are defined as val members of the evaluation.
The enum class body can include methods and other fields. The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared.
Below code uses enums with defined methods: We should define methods as abstract methods and then we have to implement defferent flavours/logic based on each enum members. Because of declaring abstract method at the enum level; all of the enum members require to implement the method.
You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: “permanent”, “temp”, “apprentice”), or flags (“execute now”, “defer execution”).
Enumerations are useful tool for creating groups of constants, such as days of the week, months of the year, and many other situations where you have a group of related, constant values. You can also use the following approach of using a Scala trait to create the equivalent of a Java enum.
It does suffer from some of the boilerplate seen with the regular Scala Enumeration too. Scala 3 has introduced a new enum keyword. Our direction enumeration can now be constructed like this:
Scala 3 has introduced a new enum keyword. Our direction enumeration can now be constructed like this: We have removed most of the boilerplate and unintuitive syntax: we are not extending a class, we don’t need to assign a type to the value, and we don’t need to assign our values. Similarly, we can add instance data to the enumerated instances:
If you want to use the Scala-defined enums as Java enums, you can do so by extending the class java.lang.Enum, which is imported by default, as follows: The type parameter comes from the Java enum definition and should be the same as the type of the enum.
If you declare your enum
as Java-compatible, you can use Java reflection to get the array of its values using the Class.getEnumConstants
method.
To declare a Java-compatible enum
it has to extend the Enum
class:
enum Color extends Enum[Color]:
case Red, Green, Blue
You can use getEnumConstants
on the static class to get correctly typed Array
of values:
val values: Array[Color] = classOf[Color].getEnumConstants
But if you want to use it in a generic manner, I believe you have to cast the Class
or the Array
to the correct type with asInstanceOf
:
def enumValues[E <: Enum[E] : ClassTag]: Array[E] =
classTag[E].runtimeClass.getEnumConstants.asInstanceOf[Array[E]]
Subtype declaration <: Enum[E]
is not strictly needed there, but is used to avoid calling it with unrelated classes and causing runtime exceptions.
Now a method enumDescr
can be written in a similar way:
def enumDescr[E <: Enum[E] : ClassTag]: String =
val cl = classTag[E].runtimeClass.asInstanceOf[Class[E]]
s"${cl.getName}: ${cl.getEnumConstants.mkString(", ")}"
And called like this:
scala> enumDescr[Color]
val res0: String = Color: Red, Green, Blue
If you want just the names of the enum cases, you can get them using scala.deriving.Mirror
(thanks to @unclebob for the idea):
import scala.deriving.Mirror
import scala.compiletime.{constValue, constValueTuple}
enum Color:
case Red, Green, Blue
inline def enumDescription[E](using m: Mirror.SumOf[E]): String =
val name = constValue[m.MirroredLabel]
val values = constValueTuple[m.MirroredElemLabels].productIterator.mkString(", ")
s"$name: $values"
@main def run: Unit =
println(enumDescription[Color])
This prints:
Color: Red, Green, Blue
You can use Scala 3 macro to generate the call to values
on the companion object.
Macro definitions from the same file can't be called, so the macro has to be placed in a separate file:
/* EnumValues.scala */
import scala.quoted.*
inline def enumValues[E]: Array[E] = ${enumValuesImpl[E]}
def enumValuesImpl[E: Type](using Quotes): Expr[Array[E]] =
import quotes.reflect.*
val companion = Ref(TypeTree.of[E].symbol.companionModule)
Select.unique(companion, "values").asExprOf[Array[E]]
Then in the main file:
enum Color:
case Red, Green, Blue
// Usable from `inline` methods:
inline def genericMethodTest[E]: String =
enumValues[E].mkString(", ")
@main def run: Unit =
println(enumValues[Color].toSeq)
println(genericMethodTest[Color])
I don't see any common trait shared by all enum
companion objects.
You still can invoke the values
reflectively, though:
import reflect.Selectable.reflectiveSelectable
def descrEnum(e: { def values: Array[?] }) = e.values.mkString(",")
enum Foo:
case Bar
case Baz
descrEnum(Foo) // "Bar,Baz"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With