For a project of mine I have implemented a Enum based upon
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = Li
Here is a simple macro based implementation:
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
abstract class Enum[E] {
def values: Seq[E] = macro Enum.caseObjectsSeqImpl[E]
}
object Enum {
def caseObjectsSeqImpl[A: c.WeakTypeTag](c: blackbox.Context) = {
import c.universe._
val typeSymbol = weakTypeOf[A].typeSymbol.asClass
require(typeSymbol.isSealed)
val subclasses = typeSymbol.knownDirectSubclasses
.filter(_.asClass.isCaseClass)
.map(s => Ident(s.companion))
.toList
val seqTSymbol = weakTypeOf[Seq[A]].typeSymbol.companion
c.Expr(Apply(Ident(seqTSymbol), subclasses))
}
}
With this you could then write:
sealed trait Currency
object Currency extends Enum[Currency] {
case object USD extends Currency
case object EUR extends Currency
}
so then
Currency.values == Seq(Currency.USD, Currency.EUR)
Since it's a macro, the Seq(Currency.USD, Currency.EUR)
is generated at compile time, rather than runtime. Note, though, that since it's a macro, the definition of the class Enum
must be in a separate project from where it is used (i.e. the concrete subclasses of Enum
like Currency
). This is a relatively simple implementation; you could do more complicated things like traverse multilevel class hierarchies to find more case objects at the cost of greater complexity, but hopefully this will get you started.
The article "“You don’t need a macro” Except when you do" by Max Afonov maxaf describes a nice way to use macro for defining enums.
The end-result of that implementation is visible in github.com/maxaf/numerato
Simply create a plain class, annotate it with
@enum
, and use the familiarval ... = Value
declaration to define a few enum values.The
@enum
annotation invokes a macro, which will:
- Replace your
Status
class with asealed Status
class suitable for acting as a base type for enum values. Specifically, it'll grow a(val index: Int, val name: String)
constructor. These parameters will be supplied by the macro, so you don't have to worry about it.- Generate a
Status
companion object, which will contain most of the pieces that now makeStatus
an enumeration. This includes a values:List[Status]
, plus lookup methods.Give the above
Status enum
, here's what the generated code looks like:
scala> @enum(debug = true) class Status {
| val Enabled, Disabled = Value
| }
{
sealed abstract class Status(val index: Int, val name: String)(implicit sealant: Status.Sealant);
object Status {
@scala.annotation.implicitNotFound(msg = "Enum types annotated with ".+("@enum can not be extended directly. To add another value to the enum, ").+("please adjust your `def ... = Value` declaration.")) sealed abstract protected class Sealant;
implicit protected object Sealant extends Sealant;
case object Enabled extends Status(0, "Enabled") with scala.Product with scala.Serializable;
case object Disabled extends Status(1, "Disabled") with scala.Product with scala.Serializable;
val values: List[Status] = List(Enabled, Disabled);
val fromIndex: _root_.scala.Function1[Int, Status] = Map(Enabled.index.->(Enabled), Disabled.index.->(Disabled));
val fromName: _root_.scala.Function1[String, Status] = Map(Enabled.name.->(Enabled), Disabled.name.->(Disabled));
def switch[A](pf: PartialFunction[Status, A]): _root_.scala.Function1[Status, A] = macro numerato.SwitchMacros.switch_impl[Status, A]
};
()
}
defined class Status
defined object Status
A late answer, but anyways...
As wallnuss said, knownDirectSubclasses
is unreliable as of writing and has been for quite some time.
I created a small lib called Enumeratum (https://github.com/lloydmeta/enumeratum) that allows you to use case objects as enums in a similar way, but doesn't use knownDirectSubclasses
and instead looks at the body that encloses the method call to find subclasses. It has proved to be reliable thus far.