正如在SO上多次讨论的那样,如果你没有详尽地列出从密封类派生的所有类型,Scala匹配将警告你。
我想要的是编译时生成的从特定父派生的案例对象的Iterable。或者,我很高兴有一种方法可以让编译器告诉我在某些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)
请告诉我这是不可能的,随意回答。看起来它应该在某种程度上是可能的,因为在确定匹配是非详尽的时,编译器必须完成相同的工作。
以另一种方式思考,除了应用于case对象之外,我会采用类似Enumeration.values()方法的方法。当然,我可以添加类似于上面代码的东西,并将一个手动维护的值列表添加到父对象的对象中,但是当编译器可以为我做这件事时,这似乎不必要地容易出错。
// Manually maintained list of values
object Parent {
val values = Seq(A, B, C)
}
答案 0 :(得分:24)
更新。从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)
这很可怕,但我认为如果不编写编译器插件,你就不会得到你真正想要的东西。
答案 1 :(得分:11)
以下是使用2.10.0-M6上的宏的工作示例:
(更新:为了使这个例子在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
上失败),并且它仍然需要一些不愉快的转换,但它可以作为一个快速的概念验证。
首先我们在启用宏的情况下编译此文件:
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
的{{1}}上收到编译时错误:
C
请注意,我们需要使用指示父级的显式类型参数帮助编译器输出。