从具有参数化类型参数的另一个方法中调用具有参数化类型参数的方法时,保留特定的结果类型参数

时间:2019-04-19 16:05:49

标签: scala generics inheritance methods existential-type

假设我正在尝试使用继承为实体,元数据和存储库建模

trait EntityMetadata {
  def maybeVersion: Option[Int]
}

// For creation
case object NoMetadata extends EntityMetadata {
  def maybeVersion: Option[Int] = None
}

// For other cases
final case class VersionedMetadata(version: Int) extends EntityMetadata {

  def maybeVersion: Option[Int] = Some(version)
}

trait Entity[Meta <: EntityMetadata] {
  type Id
  def id: Id
  def meta: Meta // Meta is paremeterised
}

如果我随后尝试创建一个特征以容纳通用后备存储的某些方法,即使类型类型已知,我似乎也喜欢...我实际上无法正确使用它们吗?

trait BackingStore {

  // Method for retrieving an entity by id
  // `Meta` doesn't really matter here, but we can't
  // wild-card it. We return the Entity with VersionedMetadata
  // since it's been stored if we can find it
  def getFromStore[Meta <: EntityMetadata, E[_] <: Entity[_]](
    id: E[Meta]#Id
  ): Option[E[VersionedMetadata]]

  // Just for demo purposes, try to retrieve something by id
  // and return its metadata version
  def getVersion[Meta <: EntityMetadata, E[_] <: Entity[_]](
    id: E[Meta]#Id
  ): Option[Long] = getFromStore(id).map { retrieved =>
    // So far so good, we know it's E[VersionedMetadata]
    val typeTest: E[VersionedMetadata] = retrieved

    //
    // value version is not a member of _$2
    // typeTest.meta.version // complains about version
    //
    retrieved.meta.version // complains about version

  }

}

我正在尝试:

  1. 为什么编译器认为retrieved.meta没有.version,或者实际上没有任何Any / Object拥有的东西。
  2. 我可以做些什么来做这项工作

1 个答案:

答案 0 :(得分:2)

尝试修复签名

def getFromStore[Meta <: EntityMetadata, E[M <: EntityMetadata] <: Entity[M]](
  id: E[Meta]#Id
): Option[E[VersionedMetadata]]

def getVersion[Meta <: EntityMetadata, E[M <: EntityMetadata] <: Entity[M]](
  id: E[Meta]#Id
): Option[Long]

E[_]Entity[_]中的E[_] <: Entity[_]是不同的:E[_]type constructor(即每种类型都可以有类型E[M] M),又称Entity[_] Entity[Meta] forSome { type Meta }existential type。现有类型没有.versionretrieved.meta的类型为Any)。


另一种修复代码的方法是

def getFromStore[Meta <: EntityMetadata, E[_] <: Entity[_]](
  id: E[Meta]#Id
): Option[E[VersionedMetadata]]

def getVersion[Meta <: EntityMetadata, E[_] <: Entity[_ <: EntityMetadata]](
  id: E[Meta]#Id
): Option[Int] = getFromStore(id).flatMap { retrieved =>
  val typeTest: E[VersionedMetadata] = retrieved

  retrieved.meta.maybeVersion
}

我保留了类型构造函数和存在性类型,但在存在性类型<: EntityMetadata的参数中添加了上限Entity[_ <: ...],这是类型参数E[_] <: ...的上限。现在retrieved.meta的类型是EntityMetadata的子类型,因此它具有.maybeVersion而不是.version(并且map应该替换为flatMap) 。同样,Long也应替换为Int

或者您可以将上限_ <: VersionedMetadata代替我的<: EntityMetadata。然后,您可以保留.version.mapLong