Juan Basso
Juan Basso

Reputation: 43

How to create an object attribute from polymorphic type in scala

I am trying to create an object that store the instances that object created and avoid to re-load. I tried to create a mutable map on the object to store it, but I get a compilation error.

This is my code:

class Bar

class FooBar extends Bar

object Foo {
    val loaded = scala.collection.mutable.Map[String, Bar]()

    def apply[T <: Bar](implicit m: Manifest[T]): T = {
        val className = m.toString()
        if (loaded.contains(className)) {
            return loaded(className)
        }
        val instance = m.runtimeClass.newInstance().asInstanceOf[T]
        loaded(className) = instance
        instance
    }
}

But when I try to compile I get the error:

error: type mismatch;
 found   : Bar
 required: T
       return loaded(className)

There is another way to create the map dynamically and pass the T? Or solve this differently?

Upvotes: 0

Views: 329

Answers (1)

Vladimir Matveev
Vladimir Matveev

Reputation: 127711

Your problem is in that you are returning loaded(className) which is of type Bar, not T as is required by your function type signature. Unfortunately, you cannot encode such dependence between types of map keys and values, at least not with the standard map collection. You have to perform explicit cast. Here is a working example:

import scala.reflect.ClassTag

class Bar

class FooBar extends Bar

object Test {
  val classMap = scala.collection.mutable.Map[Class[_], Bar]()

  def getInstance[T <: Bar : ClassTag]: T = {
    val ct = implicitly[ClassTag[T]]
    classMap get ct.runtimeClass match {
      case Some(bar) => bar.asInstanceOf[T]
      case None =>
        val instance = ct.runtimeClass.newInstance().asInstanceOf[T]
        classMap(ct.runtimeClass) = instance
        instance
    }
  }
}

object Main {
  def main(args: Array[String]) {
    println(Test.getInstance[FooBar])
    println(Test.getInstance[Bar])
    println(Test.getInstance[FooBar])
  }
}

Note that I'm using ClassTag instead of Manifest because Manifest is deprecated. Its modern equivalent is TypeTag, but for this task ClassTag is more than enough.

Update

Here are variants of the code as suggested by @0__. First:

import scala.reflect.ClassTag

class Bar

class FooBar extends Bar

object Test {
  val classMap = scala.collection.mutable.Map[Class[_], Bar]()

  def getInstance[T <: Bar : ClassTag]: T = {
    val ct = implicitly[ClassTag[T]]
    classMap get ct.runtimeClass match {
      case Some(bar: T) => bar
      case None =>
        val instance = ct.runtimeClass.newInstance().asInstanceOf[T]
        classMap(ct.runtimeClass) = instance
        instance
    }
  }
}

object Main {
  def main(args: Array[String]) {
    println(Test.getInstance[FooBar])
    println(Test.getInstance[Bar])
    println(Test.getInstance[FooBar])
  }
}

And second, the best one IMO:

import scala.reflect.ClassTag

class Bar

class FooBar extends Bar

object Test {
  val classMap = scala.collection.mutable.Map[Class[_], Bar]()

  def getInstance[T <: Bar : ClassTag]: T = {
    val ct = implicitly[ClassTag[T]]
    classMap.getOrElseUpdate(ct.runtimeClass, ct.runtimeClass.newInstance().asInstanceOf[Bar]).asInstanceOf[T]
  }
}

object Main {
  def main(args: Array[String]) {
    println(Test.getInstance[FooBar])
    println(Test.getInstance[Bar])
    println(Test.getInstance[FooBar])
  }
}

Upvotes: 1

Related Questions