微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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 }

一个类(可能会变成State 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”调用它.我有兴趣看到如何更好地完成这项工作的想法(并检查我是否已经错过了一个明显更简单的解决方案).

请注意,由于其他原因,我到目前为止选择使用抽象类型而不是参数类型(特征A扩展了Referenced [ARef]样式),但如果原因足够引人注意,可能会改变它.

解决方法

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

在下文中,我们依赖于类型Ref被密封的事实,以便我们可以简单地列举替代方案.您的Ref和Reference层次结构保持不变,我们添加一个关系类型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],cs: List[C])(ref: ARef) : Option[A] =
      as.find(_.ref == ref)
  }
  implicit val relB = new Rel[BRef,B] {
    def lookup(as: List[A],cs: List[C])(ref: BRef) : Option[B] =
      bs.find(_.ref == ref)
  }
  implicit val relC = new Rel[CRef,C] {
    def lookup(as: List[A],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
}

请注意,get的隐式Rel参数的解析从ref参数的类型计算相应Reference的类型,因此我们可以避免在get的调用站点使用任何显式类型参数.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐