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 monad 的状态类型)将包含这些类型的列表,并提供一个函数来检索对象,给定它的引用。我希望这个返回值被适当地输入,即给定

val aRef = ARef("my A ref")

我希望能够拨打如下电话:

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

并确保返回一个 Option[A],而不仅仅是一个 Option[Referenced]。到目前为止,我实现这一目标的最佳尝试如下所示:

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]样式),但如果理由足够令人信服,可以改变这一点。

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的类型计算对应Reference的类型ref,因此我们能够避免在get的调用点使用任何显式类型参数。

于 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 回答