2009-08-20 4 views
30

exemple:Comment instancier une instance de type représenté par le paramètre de type à Scala

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 
. 
. 
. 

Notez la deuxième erreur montre qu'il connaît les éléments de l'acteur sont « T » s, mais pas que le « T » est une sous-classe d'acteur, comme contrainte dans la définition générique de la classe.

Comment ce code corriger au travail (en utilisant Scala 2.8)?

+0

... j'ai oublié de mentionner, j'utilise le plugin Eclipse Scala (2.8 tous les soirs) pour cela ... –

+0

Toujours l'erreur sur "méthode mailboxSize n'est pas accessible en T", malgré l'utilisation du fac () funtion transmis comme vous l'avez suggéré. Je suis surpris par ce résultat, puisque le compilateur sait que T est <: Acteur, et que Acteur a le .mailboxSize (accédé dans la même classe de BalanceActor, comme montré) Je me demande s'il s'agit d'un bug dans la version en question de 2,8 tous les soirs j'utilise ??? Ne devrait pas l'accès à la compilation .mailboxSize, comme vous l'avez déclaré vous-même? Avez-vous quelque chose de similaire au travail, peut-être sur le plugin Eclipse 2.7.5.final, ou sur la compilation Scalac autonome? –

+0

Merci à la fois oxbow_lakes et Walter Chang pour fournir des solutions différentes, mais à la fois réalisables, pour le problème d'instanciation. –

Répondre

20

EDIT - excuses, je viens juste de remarquer votre première erreur. Il n'y a aucun moyen de instancier un T lors de l'exécution, car les informations de type est perdue lorsque votre programme est compilé (via le type effacement)

Vous devrez passer dans une usine pour réaliser la 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() 
    } 
} 

Cela peut être utilisé avec un acteur CalcActor comme suit:

val ba = new BalanceActor[CalcActor]({() => new CalcActor }) 
ba.start 

en aparté: vous pouvez utiliser until au lieu de to:

val size = 10 
0 until size //is equivalent to: 
0 to (size -1) 
+0

Exprimé votre suggestion concernant la spécification de type, mais les erreurs n'ont pas changé en conséquence –

+0

Désolé - changé ma réponse - J'ai seulement vu la 2ème erreur et n'ai pas remarqué la ligne 'new T' –

+0

Merci pour vos suggestions. Comment la présence de l'usine aide-t-elle avec l'erreur appelant une méthode spécifique aux sous-classes d'Actor (comme la mailboxSize montrée dans l'exemple)? Merci pour le rappel sur le "jusqu'à", mais il est difficile de casser des décennies d'habitude de C et Java ... ;-) –

14

Utilisation 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 = [email protected]" in console 
val foo = new Foo[Bar](bar1) 
val bar2 = foo.create // prints "bar2: Bar = [email protected]" in console 
bar2.isInstanceOf[Bar] // prints "Boolean = true" in console 

BTW, Manifest est en situation irrégulière 2.7.x afin de l'utiliser avec soin. Le même code fonctionne en 2.8.0 tous les soirs.

+0

Et ne fonctionne pas si votre A a besoin de paramètres constructeur –

+0

Votre chemin fonctionne, ainsi que oxbow_lakes ', pour résoudre le problème de création de nouvelles instances de A (T est mon exemple original) Mais ... l'erreur "méthode mailboxSize ne peut pas être consulté dans A "reste. Une idée pourquoi? –

+0

@Paul Ça me déroute aussi. "self.mailboxSize" s'exécute avec succès dans la version 2.7.5 REPL mais soulève l'erreur "méthode mailboxSize ne peut pas être accédée dans scala.actors.Actor" dans 2.8.0. "def workerMailboxSizes" se compile aussi bien en 2.7.5. –

2

Vous ne pouvez pas, comme déjà mentionné, instancier T en raison de l'effacement. Au moment de l'exécution, il n'y a pas de T. Ce n'est pas comme les templates de C++, où la substitution se produit sont à la compilation, et plusieurs classes sont réellement compilées, pour chaque variation dans l'utilisation réelle.

La solution manifeste est intéressante, mais suppose qu'il existe un constructeur pour T qui ne nécessite pas de paramètres. Vous ne pouvez pas supposer cela.

En ce qui concerne le deuxième problème, la méthode mailboxSize est protégée, de sorte que vous ne pouvez pas l'appeler sur un autre objet. Mise à jour: Ceci est vrai seulement de Scala 2.8.

11

Il y a maintenant une bonne façon et plus sûr de le faire. Scala 2.10 a introduit TypeTags, qui nous permettent en fait de surmonter le problème de l'effacement lors de l'utilisation de types génériques.

Il est maintenant possible de paramétrer votre classe comme suit:

class BalanceActor[T <: Actor :ClassTag](fac:() => T) extends Actor { 
    val actors = Array.fill[T](10)(fac()) 
} 

En faisant cela, nous exigeons d'être disponibles lorsque la classe est instancié une ClassTag implicite [T]. Le compilateur s'assurera que c'est le cas et générera du code qui passera le ClassTag [T] dans le constructeur de la classe. Le ClassTag [T] contiendra toutes les informations de type sur les T, et en raison de cette même information qui est disponible pour le compilateur au moment de la compilation (pré-effacement) sera désormais également disponible lors de l'exécution, ce qui nous permet de construire un tableau [T].

Notez qu'il est toujours pas possible de le faire:

class BalanceActor[T <: Actor :ClassTag] extends Actor { 
    val actors = Array.fill[T](10)(new T()) 
} 

La raison pour laquelle cela ne fonctionne pas est que le compilateur n'a aucun moyen de savoir si la classe T a un constructeur sans arg.

+0

Quel est l'avantage de ceci par rapport à la réponse acceptée? Votre exemple de code ne le rend pas clair. – 2rs2ts

+0

Désolé, mon exemple n'était pas génial - je l'ai amélioré maintenant. La réponse acceptée ne compile pas réellement. 'new Array [T] (workers)' ne fonctionne pas car le type T n'est pas connu au moment de l'exécution. La deuxième réponse avec Manifest était meilleure, mais TypeTags a remplacé Manifest. – Josh

+0

BTW, la raison pour laquelle cette solution est meilleure que Manifest est que l'utilisation de Manifest est dangereuse - il suppose qu'il existe un constructeur sans-arg pour T, ce qui peut ne pas être le cas. – Josh

Questions connexes