2010-07-03 15 views
19

¿En qué situaciones se deben preferir los tipos abstractos sobre los parámetros tipo?Tipos abstractos versus parámetros de tipo

+1

¿No es ya discutido en http://stackoverflow.com/questions/1154571/scala-abstract-types-vs-generics/1154727#1154727? – VonC

+1

@VonC: Vi esa respuesta, pero no la encontré satisfactoria. –

+0

Intento ilustrarlo a continuación, con una publicación reciente de Jesse EICHAR. – VonC

Respuesta

16

Para añadir a mi previous answer on Abstract type vs. parameters, también tiene las JESSE EICHAR's recent blog post (2010, 3 de mayo) poner de relieve algunas diferencias clave:

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 caso, el parámetro está "enterrado" (como un resumen interna tipo).
(excepto, como retronym pone, no está realmente enterrado, ver más abajo)

Mientras que con el tipo genérico, se menciona explícitamente el parámetro, ayudando a otras expresiones para saber qué tipo que se supone que se utiliza


Así (C1: parámetro):

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

compila, pero se exponga explícitamente el 'A' tipo que desea utilizar.

Y (C2: Tipo Resumen):

// 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) 
      ^

No compila porque 'A' nunca es mencionado en la definición p2, por lo doit no conoce al tipo de compilación lo que se supone que volver .


Al utilizar tipo abstracto y querer evitar cualquier "tipo de fuga" a la interfaz (es decir, con ganas de exponer lo que 'A' en realidad es), puede especificar un tipo muy genérico como un cambio de p2 :

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

O puede "arreglar" este tipo directamente en la función doit:
def doit(a:A):Int en lugar de def doit(a:A):A, lo que significa:
def p2(c:C2) = c.doit(c.get) compilará (incluso si p2 no menciona ningún tipo de retorno)


último (retronym 's comentario) puede especificar A ya sea de forma explícita mediante el refinado parámetro C2 Resumen:

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

O por agregando un parámetro de tipo (y refinando el tipo abstracto C2 con él!)

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

tan abstracto se recomiendan:

  • Cuando se desea ocultar una definición exacta de un miembro de tipo de código de cliente, utilice el tipo abstracto como en C2 (pero tenga cuidado con la definición de función usando C2)
  • Cuando desee sobrescribir el tipo de forma coherente en las subclases de C2, utilizar tipo abstracto (con extracción de tipo acotado)
  • Cuando se quiere mezclar en las definiciones de los tipos C2 a través de los rasgos, utilizar tipo abstracto (que no tendrá 'A' para hacer frente a la hora de mezclar con C2 su clase: se mezclan solamente C2)

Para el resto, donde sencilla tipo de instancias es necesario, parámetros de uso.
(si usted sabe que no será necesaria una extensión, pero todavía tiene que manejar varios tipos: eso es lo que los tipos de parámetros son para)


retronym añade:

Los diferencias principales están

  • varianza: C2 sólo puede ser invariantes en A,
  • la forma en que los miembros de tipo pueden ser overriden selectivamente en un subtipo (mientras que los parámetros de tipo deben volver a declarar y se pasan al supertipo)

(como 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

No está realmente enterrado: 'def p2 (c: C2 {tipo A = Int}): Int = c.doit (c.get)'. O bien: 'def p2 [X] (c: C2 {tipo A = X}): X = c.doit (c.get)'. Las principales diferencias son la varianza ('C2' solo puede ser invariante en' A') y la forma en que los miembros de tipo pueden ser selectivamente anulados en un subtipo, mientras que los parámetros de tipo deben ser redeclarados y pasados ​​al supertipo. – retronym

+1

@retronym: * "Las principales diferencias son la varianza ... y la forma en que los miembros de tipo pueden ser selectivamente anulados" * ¿Eso significa que todo lo que es posible hacer con uno de ellos es posible hacer con el otro? Excepto por los dos aspectos que mencionas. La única otra diferencia es la sintaxis? – Lii

Cuestiones relacionadas