37

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

Qantas 94 Heavy
  • 14,790
  • 31
  • 61
  • 78
scaling_out
  • 1,023
  • 1
  • 10
  • 15
  • ... forgot to mention, I'm using the Eclipse Scala plugin (2.8 nightly) for this... – scaling_out Aug 20 '09 at 11:49
  • 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 <: .mailboxsize="" a="" access="" actor="" and="" as="" balanceactor="" bug="" class="" compilation="" compile="" does="" eclipse="" got="" have="" i="" if="" in="" is="" nightly="" of="" on="" or="" particular="" perhaps="" plugin="" same="" scalac="" shouldn="" shown="" similar="" something="" stand-alone="" stated="" that="" the="" this="" to="" using="" version="" within="" wondering="" work="" you="" yourself=""> – scaling_out Aug 20 '09 at 13:19
  • Thanks to both oxbow_lakes and Walter Chang for providing different, but both workable, solutions for the instantiation problem. – scaling_out Aug 20 '09 at 14:25

4 Answers4

27

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)
oxbow_lakes
  • 129,207
  • 53
  • 306
  • 443
  • Tried out your suggestion concerning the type specification, but the errors did not change as a result – scaling_out Aug 20 '09 at 11:48
  • Sorry - changed my answer - I only saw the 2nd error and didn't notice the `new T` line – oxbow_lakes Aug 20 '09 at 11:50
  • 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... ;-) – scaling_out Aug 20 '09 at 11:57
  • The 2nd error is erroneous - fix the first issue and it will be gone. I've edited my answer again – oxbow_lakes Aug 20 '09 at 12:19
  • 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. – oxbow_lakes Aug 20 '09 at 12:21
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.

Walter Chang
  • 11,279
  • 2
  • 44
  • 36
  • 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? – scaling_out Aug 20 '09 at 13:35
  • @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. – Walter Chang Aug 20 '09 at 14:02
  • @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. – Walter Chang Aug 20 '09 at 14:17
  • @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.x – scaling_out Aug 20 '09 at 14:24
  • @Paul They've changed the access level of mailboxSize from public to protected[actors] in revision 17883. Sorry about the confusion. – Walter Chang Aug 20 '09 at 14:51
  • 2
    As m.erasure is now deprecated, you should use m.runtimeClass.newInstance().asInstanceOf[D].get.toString – Jirka Helmich Jul 11 '14 at 05:12
  • FYI it also works with a ClassType[A] instead of a Manifest[A] – belka Jul 20 '18 at 07:53
13

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.

Josh
  • 687
  • 6
  • 21
  • What is the advantage of this over the accepted answer? Your code example doesn't make that clear. – 2rs2ts Aug 10 '15 at 21:58
  • 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. – Josh Sep 16 '15 at 21:38
  • 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. – Josh Sep 16 '15 at 21:57
  • 2
    @scaling_out 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. – akauppi Oct 12 '16 at 04:55
  • how would you modify this if the class takes constructor args? – Nick Resnick Feb 27 '18 at 05:10
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.

Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670