我是否可以获得所有案例对象的编译时列表,这些案例对象派生自Scala中的密封父代?

Lei*_*and 33 scala

正如在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)
Run Code Online (Sandbox Code Playgroud)

请告诉我这是不可能的,随意回答.它似乎应该在某种程度上是可能的,因为在确定匹配是非详尽的时,编译器必须完成基本相同的工作.

再考虑一下,除了应用于case对象之外,我会采用类似Enumeration.values()方法的方法.当然,我可以添加一些类似于上面代码的东西,并将一个手动维护的值列表添加到父对象的对象中,但是当编译器可以为我做这件事时,这似乎不必要地容易出错.

// Manually maintained list of values
object Parent { 
    val values = Seq(A, B, C)
}
Run Code Online (Sandbox Code Playgroud)

Tra*_*own 25

更新.从2.10.0-M7开始,我们将本答案中提到的方法公开为公共API的一部分.isSealedClassSymbol.isSealedsealedDescendantsClassSymbol.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
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你有这样的层次结构:

object Test {
  sealed trait Parent
  case object A extends Parent
  case object B extends Parent
  case object C extends Parent
}
Run Code Online (Sandbox Code Playgroud)

您可以获取密封类型层次结构的成员的类型符号,如下所示:

scala> sealedDescendants[Test.Parent] getOrElse Set.empty
res1: Set[reflect.runtime.universe.Symbol] = Set(object A, object B, object C)
Run Code Online (Sandbox Code Playgroud)

它很可怕,但我认为如果不编写编译器插件,你就不会得到你真正想要的东西.


Tra*_*own 12

这是一个在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!")
  }
}
Run Code Online (Sandbox Code Playgroud)

这显然不是非常强大(例如,它假设您只在层次结构中有对象,并且它将失败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))
}
Run Code Online (Sandbox Code Playgroud)

我们将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
Run Code Online (Sandbox Code Playgroud)

请注意,我们需要使用指示父级的显式类型参数来帮助编译器输出.

  • 提交[9abf74be15672ce4ec1900a6b26fbf35cbce5866](9abf74be15672ce4ec1900a6b26fbf35cbce5866)引入了`knownDirectSubclasses`,这应该足以避免强制转换.我不知道是否继续2.10. (2认同)