自定义Scala枚举,搜索最优雅的版本

wal*_*uss 11 enums scala scala-2.10 scala-macros

对于我的一个项目,我已经实现了一个基于的枚举

trait Enum[A] {
  trait Value { self: A =>
    _values :+= this
  }
  private var _values = List.empty[A]
  def values = _values
}

sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
  case object EUR extends Currency
  case object GBP extends Currency
}
Run Code Online (Sandbox Code Playgroud)

来自Case对象与Scala中的枚举.我工作得很好,直到遇到以下问题.Case对象似乎很懒惰,如果我使用Currency.value,我可能实际上得到一个空列表.可以在启动时对所有枚举值进行调用,以便填充值列表,但这将有点击败这一点.

因此,我冒险进入scala反射的黑暗和未知的地方,并根据以下SO答案提出了这个解决方案.我是否可以获得所有案例对象的编译时列表,这些案例对象派生自Scala中的密封父代?我怎样才能获得通过斯卡拉2.10反射提到了实际的对象?

import scala.reflect.runtime.universe._

abstract class Enum[A: TypeTag] {
  trait Value

  private def sealedDescendants: Option[Set[Symbol]] = {
    val symbol = typeOf[A].typeSymbol
    val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]
    if (internal.isSealed)
      Some(internal.sealedDescendants.map(_.asInstanceOf[Symbol]) - symbol)
    else None
  }

  def values = (sealedDescendants getOrElse Set.empty).map(
    symbol => symbol.owner.typeSignature.member(symbol.name.toTermName)).map(
    module => reflect.runtime.currentMirror.reflectModule(module.asModule).instance).map(
    obj => obj.asInstanceOf[A]
  )
}
Run Code Online (Sandbox Code Playgroud)

令人惊讶的部分是它实际上是有效的,但它很丑陋,我会感兴趣,如果有可能使这更简单,更优雅,并摆脱asInstanceOf调用.

kon*_*ong 14

这是一个简单的基于宏的实现:

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))
  }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,你可以写:

sealed trait Currency
object Currency extends Enum[Currency] {
  case object USD extends Currency
  case object EUR extends Currency
}
Run Code Online (Sandbox Code Playgroud)

那么

Currency.values == Seq(Currency.USD, Currency.EUR)
Run Code Online (Sandbox Code Playgroud)

因为它是一个宏,所以它Seq(Currency.USD, Currency.EUR)是在编译时生成的,而不是运行时生成的.但请注意,由于它是一个宏,因此它的定义class Enum必须位于与其使用位置不同的项目中(即Enum类似的具体子类Currency).这是一个相对简单的实现; 你可以做更复杂的事情,比如遍历多级类层次结构,以更复杂的代价找到更多案例对象,但希望这会让你开始.