Могу ли я получить список времени компиляции всех объектов case, которые получены из запечатанного родителя в Scala?
Как уже много раз обсуждалось на SO, совпадение Scala предупредит вас, если вы не исчерпываете список всех типов, происходящих из запечатанного класса.
То, что я хочу, это сгенерированный во время компиляции Iterable объектов case, производных от конкретного родителя. В качестве альтернативы, я был бы рад, если бы компилятор сказал мне, что в некоторых Iterable нет всех необходимых типов. Я не хочу подход, основанный на рефлексии.
В качестве примера второго подхода я хотел бы, чтобы следующий грубый код генерировал ошибку компиляции, где это указано.
sealed trait Parent
case object A extends Parent
case object B extends Parent
case object C extends Parent
// I want a compiler error here because C is not included in the Seq()
val m = Seq(A, B).map(somethingUseful)
Не стесняйтесь ответить, сказав, что это невозможно. Просто кажется, что это должно быть возможно на некотором уровне, потому что компилятор должен выполнять по существу ту же самую работу, когда определение соответствия не является исчерпывающим.
Думая об этом по-другому, я бы взял что-то вроде метода Enumeration.values (), за исключением применения к объектам case. Конечно, я мог бы добавить что-то похожее к приведенному выше коду с поддерживаемым вручную списком значений к объекту-компаньону родителя, но это кажется излишне подверженным ошибкам, когда компилятор может сделать это для меня.
// Manually maintained list of values
object Parent {
val values = Seq(A, B, C)
}
2 ответа
Обновить. Начиная с 2.10.0-M7, мы представляем методы, упомянутые в этом ответе, как часть публичного API. isSealed
является ClassSymbol.isSealed
а также sealedDescendants
является ClassSymbol.knownDirectSubclasses
,
Это не будет ответом на ваш вопрос.
Но, если вы готовы согласиться на что-то более похожее Enumeration.values()
, и вы используете недавнюю веху 2.10, и вы готовы взяться за дело с каким-то уродливым бизнесом приведения к внутренним API, вы можете написать следующее:
import scala.reflect.runtime.universe._
def sealedDescendants[Root: TypeTag]: Option[Set[Symbol]] = {
val symbol = typeOf[Root].typeSymbol
val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]
if (internal.isSealed)
Some(internal.sealedDescendants.map(_.asInstanceOf[Symbol]) - symbol)
else None
}
Теперь, если у вас есть такая иерархия:
object Test {
sealed trait Parent
case object A extends Parent
case object B extends Parent
case object C extends Parent
}
Вы можете получить символы типа для членов иерархии запечатанных типов следующим образом:
scala> sealedDescendants[Test.Parent] getOrElse Set.empty
res1: Set[reflect.runtime.universe.Symbol] = Set(object A, object B, object C)
Это отвратительно, но я не думаю, что вы получите то, что на самом деле хотите, не написав плагин для компилятора.
Вот рабочий пример использования макросов на 2.10.0-M6:
(обновление: чтобы этот пример работал в 2.10.0-M7, вам необходимо заменить c.TypeTag на c.AbsTypeTag; чтобы этот пример работал в 2.10.0-RC1, необходимо заменить c.AbsTypeTag на c.WeakTypeTag)
import scala.reflect.makro.Context
object SealednessMacros {
def exhaustive[P](ps: Seq[P]): Seq[P] = macro exhaustive_impl[P]
def exhaustive_impl[P: c.TypeTag](c: Context)(ps: c.Expr[Seq[P]]) = {
import c.universe._
val symbol = typeOf[P].typeSymbol
val seen = ps.tree match {
case Apply(_, xs) => xs.map {
case Select(_, name) => symbol.owner.typeSignature.member(name)
case _ => throw new Exception("Can't check this expression!")
}
case _ => throw new Exception("Can't check this expression!")
}
val internal = symbol.asInstanceOf[scala.reflect.internal.Symbols#Symbol]
if (!internal.isSealed) throw new Exception("This isn't a sealed type.")
val descendants = internal.sealedDescendants.map(_.asInstanceOf[Symbol])
val objs = (descendants - symbol).map(
s => s.owner.typeSignature.member(s.name.toTermName)
)
if (seen.toSet == objs) ps else throw new Exception("Not exhaustive!")
}
}
Это, очевидно, не очень надежно (например, предполагается, что у вас есть только объекты в иерархии, и он потерпит неудачу на A :: B :: C :: Nil
), и это все еще требует некоторого неприятного кастинга, но это работает как быстрое подтверждение концепции.
Сначала мы скомпилируем этот файл с включенными макросами:
scalac -language:experimental.macros SealednessMacros.scala
Теперь, если мы попытаемся скомпилировать файл следующим образом:
object MyADT {
sealed trait Parent
case object A extends Parent
case object B extends Parent
case object C extends Parent
}
object Test extends App {
import MyADT._
import SealednessMacros._
exhaustive[Parent](Seq(A, B, C))
exhaustive[Parent](Seq(C, A, B))
exhaustive[Parent](Seq(A, B))
}
Мы получим ошибку во время компиляции на Seq
с пропавшим без вести C
:
Test.scala:14: error: exception during macro expansion:
java.lang.Exception: Not exhaustive!
at SealednessMacros$.exhaustive_impl(SealednessMacros.scala:29)
exhaustive[Parent](Seq(A, B))
^
one error found
Обратите внимание, что нам нужно помочь компилятору с явным параметром типа, указывающим на родителя.