2010-11-29 6 views
8

Je voudrais avoir un trait scellé qui a une méthode déclarée qui renvoie la classe réelle qui étend le trait. Dois-je utiliser un type abstrait, un type de paramètre ou y a-t-il une autre façon sympa de résoudre ce problème?Extension d'un trait et types

sealed trait Foo { 
    type T 
    def doit(other: T): T 
} 

ou

sealed trait Foo[T] { 
    def doit(other: T): T 
} 

Notez que T doit être un sous-type de Foo dans cet exemple. Si je le fais comme ce type d'information se sent trop répété:

case class Bar(name: String) extends Foo[Bar] { 
    def doit(other: Bar): Bar = ... 
} 

Répondre

2

Vous pouvez réduire la répétition un peu en ayant votre méthode doit retour d'une fonction d'usine:

trait Foo[T] { 
    self: T => 
    def doit: T => T 
} 

case class Bar(name: String) extends Foo[Bar] { 
    // note: types omitted 
    def doit = { other => Bar(name + other.name) } 
} 

Il est impossible de faire la même chose avec un type abstrait:

trait Foo { 
    self: T => // won't compile because T isn't defined yet 
    type T 
    def doit: T => T 
} 
1

EDIT - Voici ma réponse originale. Votre commentaire indique que vous souhaitez renvoyer une instance arbitraire d'un type correspondant, mais je ne crois pas vraiment que cela soit sensé. Supposons qu'il était, par la syntaxe T.type:

trait T { def foo : T.type } 

trait U extends T { def foo = new U } //must be a U 

class W extends U 

val w : W = (new W).foo //oh dear. 

Ceci est réalisable via this.type:

scala> trait T { 
| def foo : this.type 
| } 
defined trait T 

scala> class W extends T { 
| def foo = this 
| } 
defined class W 

scala> (new W).foo 
res0: W = [email protected] 

scala> res0.foo 
res1: res0.type = [email protected] 

Et puis aussi:

scala> ((new W) : T) 
res4: T = [email protected] 

scala> res4.foo.foo.foo 
res5: res4.type = [email protected] 
+0

sons familiers. Pourriez-vous nous donner un exemple? – chrsan

+0

Cela ne semble pas fonctionner si je veux retourner une nouvelle instance du même type? – chrsan

+0

Oui - 'this.type' dépend du chemin: il est seulement valide pour retourner l'instance en cours –

4

Ils sont pour la plupart interchangeables. Selon Odersky, la raison en était principalement la complétude: De même que le fait que les méthodes et les champs (valeurs) peuvent être soit abstraits, soit transmis en tant que paramètres, les types peuvent également l'être.

Il est préférable d'utiliser un type abstrait lorsque vous avez l'intention de mélanger plusieurs caractères qui utilisent tous le même nom de type. Avec des paramètres de type vous devez passer explicitement le type à chaque

Voici un article expliquant tout cela: http://www.artima.com/weblogs/viewpost.jsp?thread=270195

1
trait Foo[A <: Foo[A]] 

Ce trait ne peut être mélangé si A est un sous-type de Foo [A] et le seul type satisfaisant c'est la classe Foo qui est mélangée. J'ai vu cette solution dans les traits de Mapper dans Lift.

+0

Pas tout à fait.Essayez 'la classe Bar extends Foo [Bar]' et 'class Baz étend Foo [Bar]'. –

2

Vous pouvez écrire:

trait Foo[T] { 
    self:T => 
    def doit(other: T): T 
} 

case class Bar(name: String) extends Foo[Bar] { 
    def doit(other: Bar): Bar = ... 
} 

La différence à votre exemple est que la barre ne peut pas être instancié de toute autre manière (par exemple case class Bar(name: String) extends Foo[String]).