tipos programacion orientada objetos instanciar ejemplos datos crear concepto clases clase abstractos scala type-parameter abstract-type

scala - programacion - Tipos abstractos versus parámetros de tipo



tipos de datos abstractos ejemplos (1)

¿En qué situaciones deberían preferirse los tipos abstractos a los parámetros de tipo?


Para agregar a mi respuesta anterior en Tipo abstracto vs. parámetros , también tiene la publicación reciente del blog de JESSE EICHAR (2010, 3 de mayo) que destaca 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 el caso C2 , el parámetro es "enterrado" (como un tipo abstracto interno).
(excepto que, como lo dice el retronimo, no está realmente enterrado, ver más abajo)

Mientras que con el tipo genérico, el parámetro se menciona explícitamente, lo que ayuda a otras expresiones a saber qué tipo deben utilizar.

Entonces (C1: parámetro):

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

Se compila, pero usted expone explícitamente el tipo de '' A '' que desea utilizar.

Y (C2: tipo abstracto):

// 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 se compila porque '' A '' nunca se menciona en la definición de p2, por lo que no sabe en el tipo de compilación qué se supone que debe devolver.

Al usar el tipo abstracto y querer evitar cualquier "pérdida de tipo" en la interfaz (es decir, querer exponer lo que realmente es '' A ''), puede especificar un tipo muy genérico como retorno para p2:

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

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

Finalmente (el comentario del retronym ) puede especificar A ya sea explícitamente refinando el parámetro abstracto C2:

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

O 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

Se recomiendan tan abstractos:

  • Cuando desee ocultar la definición exacta de un miembro de tipo del código de cliente , use el tipo abstracto como en C2 (pero desconfíe de la definición de función que usa C2 )
  • Cuando desee reemplazar el tipo covariante en las subclases de C2 , use el tipo abstracto (con abstracción de tipo acotado)
  • Cuando quiera mezclar las definiciones de esos tipos de C2 través de rasgos , use el tipo abstracto (no tendrá '' A '' para tratar cuando mezcle C2 con su clase: solo mezcle C2 )

Para el resto, donde se necesita una instanciación de tipo simple, use Parámetros.
(Si sabe que no se necesitará ninguna extensión, pero todavía tiene que manejar varios tipos: para eso son los tipos de parámetros)

retronym agrega:

Las principales diferencias son.

  • varianza : C2 solo puede ser invariante en A ,
  • la forma en que los miembros de tipo pueden sobrescribirse de forma selectiva en un subtipo (mientras que los parámetros de tipo se deben volver a declarar y pasar al supertipo)

(como se ilustra aquí :

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(...) }

)