使用更高通道类型的通用约束Map?

时间:2013-08-17 13:02:46

标签: scala generics constraints higher-kinded-types

我正在尝试实现一个结果缓存(Loader类),它将类Resolver[T]的可哈希实例映射到Result个实例,其中T的类型为{{ 1}}。当然,用户可以专门提供Result类,提供特定于应用程序的额外方法。关键是使用Result方法,用户还必须指定结果类型:

Loader.get

我想要的是避免强制转换并使其隐含,因此代码应该类似于:

    val s = Loader.get(new SpecializedResolver()).asInstanceOf[SpecializedResult]

我目前的代码如下:

    val implicitS = Loader.get(new SpecializedResolver())

由于我是scala语言的新手,我的问题是:是否有可能通过使用scala提供的更高级的类型功能来实现这种隐式转换?我看了一下演讲'High Wizardry in the Land of Scala',演讲者Daniel Spiewak介绍了一个高阶import scala.language.existentials abstract class Result { def computed: Boolean } abstract class Resolver[T <: Result] { def result(): T forSome {type T <: Result} } class SpecializedResult extends Result { def computed = true def special() = 42 } class SpecializedResolver extends Resolver[SpecializedResult] { def result = new SpecializedResult } object Loader { val cache = collection.mutable.Map[K forSome {type K <: Resolver[_]}, V forSome {type V <: Result}]() def get[K <: Resolver[_]](k: K): V forSome {type V <: Result} = { cache.getOrElseUpdate(k, { k.result } ) } } object Runner { def main(args: Array[String]) { val s = Loader.get(new SpecializedResolver()).asInstanceOf[SpecializedResult] println("Value is: %d".format(s.special)) val implicitS = Loader.get(new SpecializedResolver()) // test.scala:34: error: value special is not a member of Result println("Value is: %d".format(implicitS.special)) } } ,它做了类似的事情,但我仍然不太清楚如何使他的解决方案适应我的情况。

2 个答案:

答案 0 :(得分:3)

首先,你不需要这里所有的存在。在某些情况下,它们只是过于冗长 - 例如,K forSome { type K <: Resolver[_] }K <: Resolver[_]完全相同。在其他情况下,他们会引入似乎无意的行为。例如:

class FooResult extends Result { def computed = true }
class BarResult extends Result { def computed = true }

object Foo extends Resolver[FooResult] { def result() = new BarResult }

这样编译得很好,因为返回类型T中的result会影响类的类型参数T

接下来,您是否考虑过懒惰值?他们会为您处理这种缓存 - 您只需在result实现中将lazy val result = ...定义为Resolver,结果将只计算一次(首次需要时)。

如果您决定需要手动处理此缓存,那么您可以通过强制转换来欺骗编译器。例如,给定以下设置:

trait Result { def computed: Boolean }
trait Resolver[T <: Result] { def result(): T }

我们可以写:

object Loader {
  private[this] val cache = collection.mutable.Map.empty[Resolver[_], Result]

  def get[V <: Result](k: Resolver[V]): V =
    cache.getOrElseUpdate(k, k.result()).asInstanceOf[V]

}

我们知道类型会有效,因为对只有一种方法可以进入地图。

更新:我的Loader解决方案基本上与谢尔盖相同(请注意,锁定地图是个好主意,并且在result调用时不需要括号,因为getOrElseUpdate的第二个参数是按名称)。无论如何我都会留下我的答案,因为前半部分是重要的部分。

答案 1 :(得分:2)

在您的情况下,您可以使用简单的解决方案(仅更改代码):

abstract class Resolver[T <: Result] {
    def result(): T
}
object Loader {
  val cache = collection.mutable.Map[Resolver[_], Any] ()

  def get[K <: Result](k: Resolver[K]): K = {
    cache.getOrElseUpdate(k, { k.result } ).asInstanceOf[K]
  }
}