在Scala中记住/缓存的请求的类型擦除

问题描述

我在Scala中收到类型删除警告。

问题是我需要缓存传出的请求。由于当前的设置方式,请求可以包装不同的返回类型。

我试图通过在getOrPut方法中添加类型参数来解决该问题。但是,在match语句中,由于类型擦除,将来的包装内容不会得到检查。

我可以使用@unchecked来消除类型擦除警告,但是我想知道是否有更好的方法来确保返回的类型是所需的类型。

简化示例:

class RequestCache() {
  val underlying: scala.collection.mutable.Map[String,Future[Any] =
    scala.collection.mutable.Map()

  def getOrPut[A](
    key: String,val: Future[Request[A]]
  ): Future[Request[A]] = {
    underlying.get(key) match {
      case None => { 
        underlying.update(key,val)
        val
      }
      case Some(storedVal: Future[Request[A]]) => storedVal
    }
  }
    
}

解决方法

似乎您的Map值将为Future [Request [A]]]类型。为什么不让类RequestCache使用类型参数,并且这种方法不会出现类型擦除问题:

class RequestCache[A] {
  val underlying: scala.collection.mutable.Map[String,Future[Request[A]]] =
    scala.collection.mutable.Map()

  def getOrPut(key: String,value: Future[Request[A]]): Future[Request[A]] =
    underlying.get(key) match {
      case None =>
        underlying.update(key,value)
        value
      case Some(storedVal: Future[Request[A]]) =>
        storedVal
    }
}
,

这不能完全解决,因为您想放置所有可能类型的Future,因此一旦忘记了确切的类型,就永远不会有100%的编译时保证可以恢复它。您必须自己强制转换类型,并希望它匹配。

我看到两种解决此问题的方法:

  • 对密钥中的类型进行编码(不要忘记在密钥中!),以便您可以安全地将类型转换为
  • 以擦除类型获取结果,并尝试投射结果并处理可能的失败

第一个可以完成,例如像这样:

trait CacheKey {
  type Out
}
object CacheKey {
  type Aux[O] = CacheKey { type Out = O }
}

case object GlobalSetting extends CacheKey { type Out = String }
case class UserSetting(userID: UUID) extends CacheKey { type Out = Int }

class Cache() {
  private val underlying =
    scala.collection.mutable.Map.empty[CacheKey,Future[_]]

  def getOrPut[O](
    key: CacheKey.Aux[O],value: => Future[O] // should be by-name!
  ): Future[O] = underlying.get(key) match {
    case Some(storedVal) =>
      storedVal.asInstanceOf[Future[O]]
    case None =>
      underlying.update(key,val)
      value
  }
}

使用String,您可以尝试做某事:

cache.getOrPut("foo",intFuture)
cache.getOrPut("foo",stringFuture)

,第一个调用将设置缓存,第二个调用将返回带有无效结果类型的Future,因此,如果您进行强制转换,您将在{{1 }}。

另一种方法是始终获取结果,然后检查结果是否有效(很难进行防弹验证):

ClassCastException

该方法存在的问题是Future不会区分具有不同类型参数的泛型,例如class Cache() { private val underlying = scala.collection.mutable.Map.empty[CacheKey,(ClassTag[_],Future[_])] def getOrPut[O: ClassTag]( key: String,value: => Future[O] // should be by-name! ): Future[O] = underlying.get(key) match { case Some((originalTag,storedVal)) => if (classTag[O] == originalTag) storedVal.asInstanceOf[Future[O]] else throw new Exception("Mismatched cache types") case None => underlying.update(key,val) value } } 返回ClassTag。但是,使用任何基于运行时反射的解决方案,无论您选择什么,都会遇到这个或类似的问题。

通过反映编译时间,您可以完全避免问题,但是在任何时候您都必须了解键的类型,因此您不能只传递classTag[List[Int]] == classTag[List[String]] -因为那样您将只知道结果是true类型的-您必须将CacheKey作为类型参数传递Future[key.Out]。视情况而定,这可能是非发行或破坏交易的行为。

顺便说一句,这里CacheKey.Aux[O]应该使用名称参数-O在创建它的那一刻开始评估,因此使用语法Scala会开始评估Future,然后它将在缓存中检查同一键下是否还有其他Future。如果它早些开始和完成可能会有一些好处,但是您可能仍然会遇到逻辑混乱的情况,因为新的Future将继续执行。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...