example:
import scala.actors._
import Actor._
class BalanceActor[T <: Actor] extends Actor {
val workers: Int = 10
private lazy val actors = new Array[T](workers)
override def start() = {
for (i <- 0 to (workers - 1)) {
// error below: classtype required but T found
actors(i) = new T
actors(i).start
}
super.start()
}
// error below: method mailboxSize cannot be accessed in T
def workerMailboxSizes: List[Int] = (actors map (_.mailboxSize)).toList
.
.
.
Note the second error shows that it knows the actor items are "T"s, but not that the "T" is a subclass of actor, as constrained in the class generic definition.
How can this code be corrected to work (using Scala 2.8)?
EDIT - apologies, I only just noticed your first error. There is no way of instantiating a T
at runtime because the type information is lost when your program is compiled (via type erasure)
You will have to pass in some factory to achieve the construction:
class BalanceActor[T <: Actor](val fac: () => T) extends Actor {
val workers: Int = 10
private lazy val actors = new Array[T](workers)
override def start() = {
for (i <- 0 to (workers - 1)) {
actors(i) = fac() //use the factory method to instantiate a T
actors(i).start
}
super.start()
}
}
This might be used with some actor CalcActor
as follows:
val ba = new BalanceActor[CalcActor]( { () => new CalcActor } )
ba.start
As an aside: you can use until
instead of to
:
val size = 10
0 until size //is equivalent to:
0 to (size -1)
Use Manifest:
class Foo[A](a: A)(implicit m: scala.reflect.Manifest[A]) {
def create: A = m.erasure.newInstance.asInstanceOf[A]
}
class Bar
var bar1 = new Bar // prints "bar1: Bar = Bar@321ea24" in console
val foo = new Foo[Bar](bar1)
val bar2 = foo.create // prints "bar2: Bar = Bar@6ef7cbcc" in console
bar2.isInstanceOf[Bar] // prints "Boolean = true" in console
BTW, Manifest is undocumented in 2.7.X so use it with care. The same code works in 2.8.0 nightly as well.
There's now a proper and safer way of doing this. Scala 2.10 introduced TypeTags, which actually enable us to overcome the problem of erasure when using generic types.
It is now possible to parameterise your class as follows:
class BalanceActor[T <: Actor :ClassTag](fac: () => T) extends Actor {
val actors = Array.fill[T](10)(fac())
}
By doing this, we are requiring an implicit ClassTag[T] to be available when the class is instantiated. The compiler will ensure this is the case and will generate code which passes the ClassTag[T] into the class constructor. The ClassTag[T] will contain all type information about T, and as a result of this the same information that is available to the compiler at compile time (pre-erasure) will now also be available at run-time, enabling us to construct an Array[T].
Note that it is still not possible to do:
class BalanceActor[T <: Actor :ClassTag] extends Actor {
val actors = Array.fill[T](10)(new T())
}
The reason this doesn't work is that the compiler has no way of knowing whether class T has a no-arg constructor.
You can't, as mentioned already, instantiate T
because of erasure. At run-time, there is no T
. This is not like C++'s templates, where the substitution happens are compile-time, and multiple classes are actually compiled, for each variation in actual use.
The manifest solution is interesting, but assumes there is a constructor for T
that does not require parameters. You can't assume that.
As for the second problem, the method mailboxSize
is protected, so you can't call it on another object. Update: this is true only of Scala 2.8.