我可以得到一个从斯卡拉的密封父母派生的所有案例对象的编译时间清单吗?
正如在SO上已经讨论过很多次,如果你没有详尽地列出所有从密封类派生出来的types,Scala匹配会警告你。
我想要的是从特定的父项派生的case对象的编译时生成的Iterable。 另外,我会很高兴的一种方法,使编译器告诉我,我没有在一些Iterable所有必要的types。 我不想要一个运行时,基于reflection的方法。
作为第二种方法的一个例子,我希望以下粗略的代码在指出的地方产生一个编译错误。
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)
随意回答,告诉我这是不可能的。 在某种程度上应该是可能的,因为编译器在确定匹配不是完全的时候必须做基本相同的工作。
换个angular度思考,除了应用于大小写对象之外,我会采用类似Enumeration.values()方法的东西。 当然,我可以添加一些类似于上面的代码的手动维护的值列表父对象的伴侣,但似乎不必要的错误倾向,当编译器可以做到这一点。
// Manually maintained list of values object Parent { val values = Seq(A, B, C) }
更新。 从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 }
您可以像这样获取密封types层次结构的成员的types符号:
scala> sealedDescendants[Test.Parent] getOrElse Set.empty res1: Set[reflect.runtime.universe.Symbol] = Set(object A, object B, object C)
这是可怕的,但我不认为你会得到你真正想要的,而不编写一个编译器插件。
以下是使用2.10.0-M6macros的一个工作示例:
(更新:为了使这个例子在2.10.0-M7中工作,你需要用c.AbsTypeTag代替c.TypeTag;为了使这个例子在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
上失败),并且仍然需要一些不愉快的转换,但是它作为一个快速的概念certificate。
首先我们用启用的macros来编译这个文件:
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
上得到一个编译时错误:
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
请注意,我们需要使用显式的父types参数帮助编译器。