2010-07-03 6 views
19

Dans quelles situations les types abstraits devraient-ils être préférés aux paramètres de type?Types abstraits et paramètres de type

+1

N'est-il pas déjà discuté dans http://stackoverflow.com/questions/1154571/scala-abstract-types-vs-generics/1154727#1154727? – VonC

+1

@VonC: J'ai vu cette réponse mais je ne l'ai pas trouvée satisfaisante. –

+0

J'essaie de l'illustrer ci-dessous, avec un blog récent de Jesse EICHAR. – VonC

Répondre

16

Pour ajouter à mon previous answer on Abstract type vs. parameters, vous avez aussi les JESSE EICHAR's recent blog post (2010, 3 mai) mettant en évidence quelques différences importantes:

trait C1[A] { 
    def get : A 
    def doit(a:A):A 
} 
trait C2 { 
    type A 
    def get : A 
    def doit(a:A):A 
} 

En C2 cas, le paramètre est « enterré » (comme un résumé interne type).
(sauf, comme retronym le dit, il est pas réellement enterré, voir ci-dessous)

Alors qu'avec type générique, le paramètre est explicitement mentionné, aider d'autres expressions de savoir quel type ils sont censés utiliser


So (C1: paramètre):

//compiles 
def p(c:C1[Int]) = c.doit(c.get) 

Il compile, mais vous exposer explicitement le type 'A' que vous souhaitez utiliser.

et (C2: type abstrait):

// doesn't compile 
def p2(c:C2) = c.doit(c.get) 
<console>:6: error: illegal dependent method type 
     def p2(c:C2) = c.doit(c.get) 
      ^

Il ne compile pas parce que 'A' est jamais mentionné dans la définition de p2, donc doit ne sait pas au type de compilation ce qu'il est censé revenir .


Lorsque vous utilisez le type abstrait et vouloir éviter tout type « fuite » à l'interface (par exemple vouloir exposer ce qui A 'est en fait), vous pouvez spécifier un type très générique comme un retour pour p2 :

// compiles because the internals of C2 does not leak out 
def p(c:C2):Unit = c.doit(c.get) 

Ou vous pouvez "fixer" ce type directement dans la fonction doit:
def doit(a:A):Int au lieu de def doit(a:A):A, ce qui signifie:
def p2(c:C2) = c.doit(c.get) compilera (même si p2 ne mentionne pas tout type de retour)


Enfin (commentaire de retronym) vous pouvez spécifier A soit explicitement par le raffinage C2 paramètre abstrait:

scala> def p2(c:C2 { type A = Int }): Int = c.doit(c.get) 
p2: (c: C2{type A = Int})Int 

Ou par ajouter un paramètre type (et affiner le type abstrait C2 avec!)

scala> def p2[X](c:C2 { type A = X }): X = c.doit(c.get) 
p2: [X](c: C2{type A = X})X 

donc abstraite sont recommandées:

  • Lorsque vous souhaitez masquer une définition exacte d'un membre de type de code client, utilisez type abstrait comme dans C2 (mais se méfier de la définition de la fonction en utilisant C2)
  • Lorsque vous souhaitez remplacer le type covariant dans les sous-classes de C2, utilisez type abstrait (avec l'abstraction de type borné)
  • Lorsque vous voulez mélanger dans les définitions de ces C2 types par des traits, utilisez type abstrait (vous n'aurez pas « A » pour traiter lors du mélange C2 avec votre classe: vous mélangez seulement C2)

Pour le reste, où type simple instanciation est nécessaire, les paramètres d'utilisation.
(si vous savez que aucune prolongation ne sera nécessaire, mais vous devez toujours gérer plusieurs types: c'est-ce que les types de paramètres sont pour)


retronym ajoute:

Les principales différences sont

  • variance: C2 ne peut être invariant dans A,
  • la façon dont les éléments de type peut être sélectivement écrasées dans un sous-type (alors que les paramètres de type doivent être redéclaré et transmis au supertype)

(comme illustrating here:

trait T1 { 
    type t 
    val v: t 
} 
trait T2 extends T1 { 
    type t <: SomeType1 
} 
trait T3 extends T2 { 
    type t <: SomeType2 // where SomeType2 <: SomeType1 
} 
class C extends T3 { 
    type t = Concrete // where Concrete <: SomeType2 
    val v = new Concrete(...) 
} 

)

+6

Ce n'est pas vraiment enterré: 'def p2 (c: C2 {type A = Int}): Int = c.doit (c.get)'. Ou: 'def p2 [X] (c: C2 {type A = X}): X = c.doit (c.get)'. Les principales différences sont la variance ('C2' ne peut être invariant que dans' A'), et la manière dont les membres de type peuvent être sélectivement remplacés dans un sous-type alors que les paramètres de type doivent être redéclarés et transmis au supertype. – retronym

+1

@retronym: * "Les principales différences sont la variance ... et la manière dont les membres de type peuvent être sélectivement écrasés" * Cela signifie-t-il que tout ce qu'il est possible de faire avec l'un d'eux est possible avec l'autre? Sauf pour les deux aspects que vous mentionnez. La seule autre différence est la syntaxe? – Lii

Questions connexes