Custom Scala enum, most elegant version searched

风格不统一 提交于 2019-11-29 03:52:57
kong

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.

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.

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!