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 usaC2
) - 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 mezcleC2
con su clase: solo mezcleC2
)
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 enA
, - 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(...)
}
)