How to instantiate an instance of type represented by type parameter in Scala
Asked Answered
A

4

46

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

Acevedo answered 20/8, 2009 at 11:34 Comment(3)
... forgot to mention, I'm using the Eclipse Scala plugin (2.8 nightly) for this...Acevedo
Still getting the error on "method mailboxSize cannot be accessed in T", in spite of using the fac() funtion passed-in as you suggested. I am surprised by this result, since the compiler knows that T is <: Actor, and that Actor does have the .mailboxSize (accessed within the same BalanceActor class, as shown) I'm wondering if this is a bug in the particular version of 2.8 nightly I'm using??? Shouldn't the access to the .mailboxSize compile, as you stated yourself? Have you got something similar to work, perhaps on the 2.7.5.final Eclipse plugin, or stand-alone scalac compilation?Acevedo
Thanks to both oxbow_lakes and Walter Chang for providing different, but both workable, solutions for the instantiation problem.Acevedo
N
30

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)
Note answered 20/8, 2009 at 11:41 Comment(5)
Tried out your suggestion concerning the type specification, but the errors did not change as a resultAcevedo
Sorry - changed my answer - I only saw the 2nd error and didn't notice the new T lineNote
Thanks for your suggestions. How would the presence of the factory help with the error calling a method specific to subclasses of Actor (such as the mailboxSize shown in the example)? Thanks for the reminder about the "until," but it is hard to break decades of habit from C and Java... ;-)Acevedo
The 2nd error is erroneous - fix the first issue and it will be gone. I've edited my answer againNote
When I say "erroneous", I just mean that the error is there because your BalanceActor class has not actually correctly compiled with its generic type information.Note
C
16

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.

Cythiacyto answered 20/8, 2009 at 13:20 Comment(7)
Your way works, as well as oxbow_lakes', for solving the problem of creating new instances of A (T is my original example) But... the error "method mailboxSize cannot be accessed in A" remains. Any idea why?Acevedo
@Paul It puzzles me as well. "self.mailboxSize" executes successfully in 2.7.5 REPL but raises "method mailboxSize cannot be accessed in scala.actors.Actor" error in 2.8.0. "def workerMailboxSizes" compiles fine in 2.7.5 as well.Cythiacyto
@Paul After looking at the current source code on the trunk for Actor, I think they have removed "mailboxSize" from trait Actor. You will need to use "mailboxSize" in object Actor instead. But that's probably not what you want because it returns the mailbox size of "self" only.Cythiacyto
@Walter Chang: Thanks for checking that out.... I only looked at the javadocs for 2.8 (over at scaladocs.jcraft.com/2.8.0) which don't show the def mailboxSize removed, so I expected it to still be there as in 2.7.xAcevedo
@Paul They've changed the access level of mailboxSize from public to protected[actors] in revision 17883. Sorry about the confusion.Cythiacyto
As m.erasure is now deprecated, you should use m.runtimeClass.newInstance().asInstanceOf[D].get.toStringHammurabi
FYI it also works with a ClassType[A] instead of a Manifest[A]Alcoholicity
A
14

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.

Anklet answered 5/3, 2015 at 16:25 Comment(5)
What is the advantage of this over the accepted answer? Your code example doesn't make that clear.Heap
Sorry, my example wasn't great - I've improved it now. The accepted answer actually doesn't compile. new Array[T](workers) doesn't work because the type T is not known at runtime. The second answer with Manifest was better, but TypeTags have superseded Manifest.Anklet
BTW, the reason this solution is better than Manifest is that using Manifest is unsafe - it assumes that there is a no-arg constructor for T, which may not be the case.Anklet
@Acevedo This could be the accepted answer - things have rolled on since '09 and I feel this is the best find for people coming to the question in '16.Figurative
how would you modify this if the class takes constructor args?Brendin
M
2

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.

Manumission answered 20/8, 2009 at 14:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.