Frage

I am trying to implement a result cache (Loader class) that maps hashable instances of class Resolver[T] to Result instances, where T is has type of Result. Of course a user is given the possibility to specialize the the Result class providing extra methods that are application specific. The point is that using the Loader.get method the user has also to specify the resulting type:

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

What I want is to avoid the cast and make it implicit so the code should be something like:

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

The code I have at the moment is the following:

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))
  }
}

Since I am relatevely new to the scala language my question is: is it even possible to achieve this implicit cast by using higher-kinded-types feature that scala provides? I took a look at the talk 'High Wizardry in the Land of Scala' where the speaker Daniel Spiewak introduces a higher-order HOMap that does something similar but I still don't well understand how to adapt his solution to my case.

War es hilfreich?

Lösung

First of all, you don't need all the existentials here. In some cases they're just overly verbose—K forSome { type K <: Resolver[_] }, for example, is exactly the same as K <: Resolver[_]. In other cases they introduce behavior that seems to be unintended. For example:

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

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

This compiles just fine, because the T in the return type of result shadows the class's type parameter T.

Next, have you considered lazy values here? They'd take care of this kind of caching for you—you just define result as lazy val result = ... in your Resolver implementations and the result will be computed exactly once (when it's first needed).

If you decide you need to handle this caching manually, this is a situation where you may be able to justify bullying the compiler with a cast. For example, given the following setup:

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

We can write:

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]

}

We know that the types will work out, since there's only one way for pairs to get into the map.

Update: My Loader solution here is essentially the same as Sergey's (note though that it's a good idea to lock down the map, and that you don't need brackets around the call to result, since the second parameter of getOrElseUpdate is by-name). I'm leaving my answer anyway because the first half is the important part.

Andere Tipps

In your case you can use simple solution (only changed code):

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]
  }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top