如何从泛型函数返回正确的类型传递了相关的抽象类型参数

时间:2013-07-08 07:07:51

标签: scala

我正在尝试为以下情况编写“更好”(更惯用?)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 }

另一个类(可能会转变为状态monad的状态类型)将包含这些类型的列表,并提供一个函数来检索对象,并给出它的引用。我希望这个返回值适当地输入,即给定

val aRef = ARef("my A ref")

我希望能够拨打电话:

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

并确保取回选项[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]样式),但如果原因足够引人注意,可能会改变它。

2 个答案:

答案 0 :(得分:9)

在这种情况下,没有铸造所需的机器真的不是那么重......这只是functional dependency的另一个例子。

在下文中,我们依赖类型Ref被密封的事实,以便我们可以简单地枚举替代方案。您的RefReference层次结构保持不变,我们添加关系类型Rel以表示两者之间的类型级别对应关系,并进行适当的值级别选择,< / p>

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的呼叫站点使用任何显式类型参数。

答案 1 :(得分: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]]
  }
}