自定义Scala枚举,最优雅的版本搜索 [英] Custom Scala enum, most elegant version searched
问题描述
对于我的一个项目,我基于
For a project of mine I have implemented a Enum based upon
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
}
from 案例对象与Scala中的枚举。我工作不错,直到遇到以下问题。 case对象似乎是懒惰的,如果我使用Currency.value我可能会得到一个空的List。在启动时可能会对所有枚举值进行调用,以使得值列表将被填充,但这将是一种打败点。
from Case objects vs Enumerations in Scala. I worked quite nice, till I run into the following problem. Case objects seem to be lazy and if I use Currency.value I might actually get an empty List. It would have been possible to make a call against all Enum Values on startup so that the value list would be populated, but that would be kind of defeating the point.
所以我冒险进入黑暗和未知的scala反射的地方,并提出了这个解决方案,基于以下SO答案。 我可以从Scala的密码父级获取所有案例对象的编译时间列表?
和如何获取Scala 2.10反射引用的实际对象?
So I ventured into the dark and unknown places of scala reflection and came up with this solution, based upon the following SO answers. Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala? and How can I get the actual object referred to by Scala 2.10 reflection?
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]
)
}
惊人的部分这是它实际上是有效的,但是如果可以让这个更简单和更优雅,并且摆脱asInstanceOf调用,那么这个地狱是很丑的,我会感兴趣的。
The amazing part of this is that it actually works, but it is ugly as hell and I would be interested if it would be possible to make this simpler and more elegant and to get rid of the asInstanceOf calls.
推荐答案
这是一个简单的基于宏的实现:
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))
}
}
有了这个,你可以写:
sealed trait Currency
object Currency extends Enum[Currency] {
case object USD extends Currency
case object EUR extends Currency
}
所以然后
Currency.values == Seq(Currency.USD, Currency.EUR)
由于它是一个宏,因此在编译时生成 Seq(Currency.USD,Currency.EUR)
,而不是运行时。请注意,由于它是一个宏,因此类枚举
的定义必须位于使用它的单独项目中(即<$ c $的具体子类) c>枚举像货币
)。这是一个相对简单的实现;你可以做更复杂的事情,例如遍历多层次的层次结构,以更复杂的代价找到更多的案例对象,但希望这将让你开始。
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.
这篇关于自定义Scala枚举,最优雅的版本搜索的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!