4

私は次のような状況で「より良い」(より慣用的な?) Scala コードを書こうとしています: 次のような参照ケース クラスの並列セットに属する参照フィールドによって識別されるクラスのセットがあります。

abstract sealed class Ref(value: String)

case class ARef(value: String) extends Ref(value)
case class BRef(value: String) extends Ref(value)
case class CRef(value: String) extends Ref(value)

trait Referenced {
  type refType <: Ref
  val ref: refType
}

trait A extends Referenced { type refType = ARef }
trait B extends Referenced { type refType = BRef }
trait C extends Referenced { type refType = CRef }

別のクラス (おそらく State モナドの状態型になる) は、これらの型のリストを含み、参照を指定してオブジェクトを取得する関数を提供します。この戻り値を適切に型付けしたい、つまり与えられた

val aRef = ARef("my A ref")

私は次のような電話をかけることができるようにしたい:

val myA: Option[A] = context.get[A](aRef)

Option[Referenced] だけでなく、Option[A] を必ず返してください。これまでのところ、これを達成するための最善の試みは次のようになります。

trait Context {

  // ... other stuff ...

  protected val aList: List[A]
  protected val bList: List[B]
  protected val cList: List[C]

  def get[R <: Referenced](ref: R#refType): Option[R] = {
    val result = ref match {
      case aRef: ARef => aList.find(_.ref == aRef)
      case bRef: BRef => bList.find(_.ref == bRef)
      case cRef: CRef => cList.find(_.ref == cRef)
      case _ => throw new RuntimeException("Unknown Ref type for retrieval: "+ref)
    }
    result.asInstanceOf[Option[R]]
  }
}

これは正しく動作しているように見えますが、その中に「asInstanceOf」呼び出しが含まれています。これがどのように改善されるかについてのアイデアを見たいと思います(そして、明らかに簡単な解決策を見逃していないことを確認してください)。

他の理由で、これまでのところ、パラメーターの型 ( trait A extends Referenced[ARef]style ) ではなく抽象型付けを使用することを選択しましたが、理由が十分に説得力がある場合はこれを変更できます。

4

2 に答える 2

9

キャストせずにこれを行うために必要な機械は、この場合、それほど重くはありません...これは、機能依存の別の例にすぎません。

Ref以下では、単純に選択肢を列挙できるように型が封印されているという事実に依存しています。あなたRefReference階層は変更されず、関係タイプを追加して、Rel両者の間のタイプレベルの対応を表現し、適切な値レベルの選択を行います。

trait Rel[Ref, T] {
  def lookup(as: List[A], bs: List[B], cs: List[C])(ref: Ref) : Option[T]
}

object Rel {
  implicit val relA = new Rel[ARef, A] {
    def lookup(as: List[A], bs: List[B], cs: List[C])(ref: ARef) : Option[A] =
      as.find(_.ref == ref)
  }
  implicit val relB = new Rel[BRef, B] {
    def lookup(as: List[A], bs: List[B], cs: List[C])(ref: BRef) : Option[B] =
      bs.find(_.ref == ref)
  }
  implicit val relC = new Rel[CRef, C] {
    def lookup(as: List[A], bs: List[B], cs: List[C])(ref: CRef) : Option[C] =
      cs.find(_.ref == ref)
  }
}

Context次のように、パターンマッチやキャストなしで再実装できます。

trait Context {

  // ... other stuff ...

  protected val aList: List[A] = ???
  protected val bList: List[B] = ???
  protected val cList: List[C] = ???

  def get[R <: Ref, T](ref: R)(implicit rel: Rel[R, T]): Option[T] =
    rel.lookup(aList, bList, cList)(ref)
}

そして、この新しい定義を次のように使用できます。

object Test {
  def typed[T](t: => T) {}     // For pedagogic purposes only

  val context = new Context {}

  val aRef = ARef("my A ref")
  val myA = context.get(aRef)
  typed[Option[A]](myA)        // Optional: verify inferred type of myA

  val bRef = BRef("my B ref")
  val myB = context.get(bRef)
  typed[Option[B]](myB)        // Optional: verify inferred type of myB

  val cRef = CRef("my C ref")
  val myC = context.get(cRef)
  typed[Option[C]](myC)        // Optional: verify inferred type of myC
}

への暗黙のRel引数の解決は、対応する の型を引数getの型から計算することに注意してください。そのため、の呼び出しサイトで明示的な型引数を使用する必要がなくなります。Referencerefget

于 2013-07-08T09:56:32.127 に答える
0

他の人が提供した回答とより直接的な比較を生成するために、読者が上下に投票できるようにすることは興味深い/有益だと思ったので、私の質問に対する私自身の(現在の)「回答」を繰り返します。

trait Context {

  // ... other stuff ...

  protected val aList: List[A]
  protected val bList: List[B]
  protected val cList: List[C]

  def get[R <: Referenced](ref: R#refType): Option[R] = {
    val result = ref match {
      case aRef: ARef => aList.find(_.ref == aRef)
      case bRef: BRef => bList.find(_.ref == bRef)
      case cRef: CRef => cList.find(_.ref == cRef)
      case _ => throw new RuntimeException("Unknown Ref type for retrieval: "+ref)
    }
    result.asInstanceOf[Option[R]]
  }
}
于 2013-07-08T07:13:20.390 に答える