Пользовательский Scala enum, самый элегантный вариант поиска
Для моего проекта я внедрил Enum на основе
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 Объекты объекта vs Перечисления в Scala. Я работал неплохо, пока не столкнулся с проблемой. Объекты объекта кажутся ленивыми, и если я использую Currency.value, я могу получить пустой список. Было бы возможно сделать вызов против всех значений Enum при запуске, чтобы список значений был заполнен, но это было бы как бы победить точку.
Поэтому я отважился в темные и неизвестные места отражения scala и придумал это решение, основываясь на следующих ответах SO. Могу ли я получить список времени для всех объектов case, которые получены из запечатанного родителя в Scala?
и Как я могу получить фактический объект, на который ссылается отражение 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]
)
}
Удивительная часть этого заключается в том, что она действительно работает, но она уродливая, и я был бы заинтересован, если бы было возможно сделать это более простым и элегантным и избавиться от вызовов asInstanceOf.
Ответы
Ответ 1
Вот простая реализация на основе макросов:
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)
создается во время компиляции, а не во время выполнения. Обратите внимание, однако, что, поскольку это макрос, определение class Enum
должно быть в отдельном проекте, из которого он используется (т.е. Конкретные подклассы Enum
как Currency
). Это относительно простая реализация; вы можете делать более сложные вещи, такие как переходы многоуровневых классов, чтобы найти больше объектов case за счет большей сложности, но, надеюсь, это поможет вам начать.
Ответ 2
Поздний ответ, но в любом случае...
Как сказал wallnuss, knownDirectSubclasses
ненадежен, как написание, и был довольно долгое время.
Я создал небольшую библиотеку под названием Enumeratum (https://github.com/lloydmeta/enumeratum), которая позволяет использовать объекты case как перечисления аналогичным образом, но doesn 't использовать knownDirectSubclasses
и вместо этого смотреть на тело, которое включает вызов метода для поиска подклассов. До сих пор он оказался надежным.
Ответ 3
Статья " " Вам не нужен макрос "За исключением случаев, когда вы делаете" Макс Афонов
maxaf описывает хороший способ использования макроса для определения перечислений.
Конечный результат этой реализации виден в github.com/maxaf/numerato
Просто создайте простой класс, аннотируйте его с помощью @enum
и используйте знакомое объявление val ... = Value
для определения нескольких значений перечисления.
Аннотация @enum
вызывает макрос, который будет:
- Замените класс
Status
классом sealed Status
, подходящим для использования в качестве базового типа для значений перечисления. В частности, он вырастет конструктор (val index: Int, val name: String)
. Эти параметры будут предоставлены макросом, поэтому вам не придется беспокоиться об этом. - Сгенерируйте объект-компаньон
Status
, который будет содержать большинство частей, которые теперь делают Status
перечисление. Это включает в себя значения: List[Status]
, а также методы поиска.
Дайте выше Status enum
, вот что выглядит сгенерированный код:
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