39

SOで何度も議論されてきたように、封印されたクラスに由来するすべてのタイプを網羅的にリストしないと、Scalaマッチは警告を発します。

私が欲しいのは、特定の親から派生したケースオブジェクトのコンパイル時に生成されたIterableです。あるいは、一部のIterableに必要なすべての型がないことをコンパイラーに通知させる方法に満足しています。実行時のリフレクションベースのアプローチは必要ありません。

2番目のアプローチの例として、次の大まかなコードで、示されている場所でコンパイルエラーを生成するようにします。

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)
}
4

2 に答える 2

26

アップデート。2.10.0-M7以降、この回答に記載されているメソッドをパブリックAPIの一部として公開しています。isSealedでありClassSymbol.isSealedsealedDescendantsです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)

それは恐ろしいことですが、コンパイラプラグインを書かずに実際に欲しいものを手に入れることはできないと思います。

于 2012-08-22T20:39:57.987 に答える
13

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

親を示す明示的な型パラメーターを使用してコンパイラーを支援する必要があることに注意してください。

于 2012-08-27T01:07:08.987 に答える