Custom Scala enum, most elegant version searched

后端 未结 3 2118
借酒劲吻你
借酒劲吻你 2020-12-16 17:26

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         


        
相关标签:
3条回答
  • 2020-12-16 18:00

    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.

    0 讨论(0)
  • 2020-12-16 18:00

    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 familiar val ... = Value declaration to define a few enum values.

    The @enum annotation invokes a macro, which will:

    • Replace your Status class with a sealed 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 make Status 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
    
    0 讨论(0)
  • 2020-12-16 18:15

    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.

    0 讨论(0)
提交回复
热议问题