2011-08-27 21 views
6

Si tengo la siguiente jerarquía de tipos Scala:¿Puede un parámetro de tipo Scala referirse a sí mismo cuando se usa como parámetro para una clase base?

// Base traits 
trait TA[X <: TA[X,Y], Y <: TB[X,Y]] 
trait TB[X <: TA[X,Y], Y <: TB[X,Y]] 
trait TC[X <: TA[X,_]] 

// More specific traits 
trait TI[X <: TI[X,Y], Y <: TJ[X,Y]] extends TA[X,Y] 
trait TJ[X <: TI[X,Y], Y <: TJ[X,Y]] extends TB[X,Y] 
trait TK[X <: TI[X,_]] extends TC[X] 

// Concrete class that should implement TK, but does not compile 
class Z extends TK[TI[_,_]] 

// What is needed to be able to get class Z to compile 
// The reference of X to itself has been removed. 
trait TC2[X <: TA[_,_]] 
trait TK2[X <: TI[_,_]] extends TC2[X] 
class Z2 extends TK2[TI[_,_]] 

TC sería un gerente genérica de algún tipo de asistencia técnica.

TK sería un administrador más específico de un TA (TI) más específico.

Z sería la implementación concreta que gestiona cualquier objeto que implemente TI.

Z no es legal, pero Z2 sí lo es. Desafortunadamente, TC y TK son más específicos que TC2 y TK2. Entonces, ¿hay alguna manera de declarar Z usando TC y TK, en lugar de TC2 y TK2?

[EDIT] No pude decir en mi pregunta original, que entiendo por qué Z no es correcto. Lo que realmente quiero saber es si hay una manera de decir algo como:

class Z3 extends TK[TI[TI,_]] 

Respuesta

7

Cuando tiene límites de tipo mutuamente recursivos complejos a menudo puede ser útil ver si puede traducir su problema a uno equivalente usando miembros de tipo abstracto en su lugar.Si lo hacemos mecánicamente, su base y los rasgos más específicos terminan pareciéndose,

// Base traits 
trait TA { 
    type X <: TA 
    type Y <: TB 
} 
trait TB { 
    type X <: TA 
    type Y <: TB 
} 

trait TC { 
    self => 
    type X <: TA { type X <: self.X } 
} 

// More specific traits 
trait TI extends TA { 
    type X <: TI 
    type Y <: TJ 
} 

trait TJ extends TB { 
    type X <: TI 
    type Y <: TJ 
} 

trait TK { 
    self => 
    type X <: TI { type X <: self.X } 
} 

y ahora tenemos una definición clara de Z como,

class Z extends TK { 
    type X = TI 
} 

Tenga en cuenta que las definiciones de los miembros de TA, TB y TI, TJ son esencialmente lo mismo. Debido a que estos tipos son ahora miembros de tipo podemos factorizar a cabo en tipos de bases comunes como así,

// Base traits 
trait T0 { 
    type X <: TA 
    type Y <: TB 
} 
trait TA extends T0 
trait TB extends T0 

trait TC { 
    self => 
    type X <: TA { type X <: self.X } 
} 

// More specific traits 
trait T1 extends T0 { 
    type X <: TI 
    type Y <: TJ 
} 

trait TI extends TA with T1 
trait TJ extends TB with T1 

trait TK extends TC { 
    self => 
    type X <: TI { type X <: self.X } 
} 

class Z extends TK { 
    type X = TI 
} 
+0

Si bien puedo seguir su argumento, creo que ha solucionado el problema cambiando el significado de Z. Z era un TK, y por lo tanto un TC en mi ejemplo, pero no era, y no debería ser, una TI. Si TC es un "gerente", TA es, por ejemplo, una oficina y TB algo relacionado con la oficina que a un gerente no le importa, y TK es un gerente de contabilidad y TI una oficina contable, entonces Z sería una instancia concreta de algo que era al mismo tiempo un gerente de contabilidad y una oficina de contabilidad. Además, quiero señalar que en mi código real, TA y TB diferirían porque definirían sus propios comportamientos. –

+0

Comentario equitativo: He modificado la definición de Z para abordarlo. Tenga en cuenta que puede agregar cualquier otra definición que desee a TA, TB, TI, TJ ... simplemente no necesita repetir el tipo de miembros. –

3

Sería incorrecto. Aquí está el por qué, con un ejemplo simplificado. No necesitamos dos parámetros genéricos, que no necesitamos los subtipos de TI, TJ y los conocimientos tradicionales, ya sea

trait TA[X <: TA[X]] 
trait TC[X <: TA[X]] 
class Z extends TC[TA[_]] 

tipo argumentos [TA [_]] no son conformes al rasgo parámetro de tipo de TC límites [X < : TA [X]]

Veamos por qué esta declaración no es correcta y es correcto que falle. Agreguemos algunos códigos. Cambio TC a class, para que el código se pueda traducir a java. A trait le iría tan bien en scala.

trait TA[X <: TA[X]] {def f(x: X) } 
class TC[X <: TA[X]] {def g(x: X) = x.f(x)} 

Funciona bien, también es x: XTA[X], por lo que no tiene una rutina f, que aceptará una X.

Si tratamos lugar

class TC2[X <: TA[_]] {def g(x: X) = x.f(x)} 

continuación, se produce un error. Sabemos que hay un método f en x, pero no sabemos qué tipo se necesita como argumento, no podemos saber que x estará bien. En efecto, supongamos que definimos

class T1 extends TA[T1]] {def f(t1: T1) = {}; def t1Only = println("only in T1")} 
class T2 extends TA[T1]] {def f(t1: T1) = t1.t1Only } 

Ahora bien, si se le permitió TC2, podría crear un TC2[T2], llame g con un T2, lo que exigiría f en T2 con un T2. Esto no está permitido, y con razón, así como T2 no tiene el método t1Only.

Esto muestra por qué no puede ser TC acepta TA[_]] como parámetro, ya que ello permitirá T2, que no es compatible con el método de g. Entonces, ¿por qué no podemos definir Z con el parámetro TA[_]? Sería exactamente el mismo en Java, y con su código original también.


Editar: Me siento un poco culpable por mi respuesta. Con la razón por la que di por qué no debería permitirse, pensé que habría una solución simple. Falló, no tuve tiempo de investigar más y lo publiqué sin mencionarlo. La solución fue un tipo de uno mismo. Si hacemos

trait TA[X <: TA[X]] {self: X => } 

entonces no podemos definir T2 extends TA[T1]. Por lo tanto, es más limitado que el código original. Pero tenemos que aceptar limitaciones al código original de todos modos, porque no era correcto. Entonces no puede ser solo un truco de sintaxis, tiene que hacer cosas imposibles que no lo fueron. Pensé que T2 extends TA[T1] probablemente no era algo intencional, y que era lo único que debía evitar.

Al parecer, no fue, el mismo error. Ahora no tengo un ejemplo de por qué no debería funcionar. Lo que por supuesto no significa que no hay ninguno.

Luego eché un vistazo a la solución de Miles, preguntándome por qué la posibilidad de T2 extends TA[T1] no le hace daño. Así que de nuevo, descartando TB, Y, TI, TJ y TK:

trait TA{type X} 
trait TC{self => type X <: TA{type X <: self.X} 
class Z extends TC{type X = TA} 

Esto compila. Y podemos hacer

trait T1 extends TA{type X = T1} 
trait T2 extends TA{type X = T1} 

Pero hay una cosa que no podemos hacer:

trait TA {type X <: TA; def f(x: X)} 
trait TC {self => 
    type X <: TA{type X <: self.X} 
    def g(x: X) = x.f(x) 
} 

Obtenemos el siguiente error en g, para la x argumento para f

coincidencia de tipos; encontrado: x.type (con tipo subyacente TC.this.X) requerido: x.x

TC no es exactamente el original (como originalmente, g se dejó). Es así porque hay un type X <: self.X en TA mientras que TC[X <: TA[X]] fue el más fuerte type X = self.X. Si escribimos eso, volvemos al error original, Z no compila. Por lo tanto, este TC es algo entre el original TC (type X = self.X) y TC2 (sin conocimiento de la X de TA). De nuevo, una limitación en el código original, no podemos definir g.

Si la limitación es aceptable, está bien. No sé cómo escribirlo como genérico (ni cómo escribir el tipo de uno mismo {self : X => con un miembro de tipo abstracto). Miles es definitivamente el experto, estoy seguro de que podría decir cómo se hace o que no es posible.

+0

Miles mostró cómo podría funcionar con tipos abstractos, mientras que demostró por qué no funcionó con los genéricos. Aunque ambas respuestas son útiles, ninguna de las dos me dice si la "clase Z3 extiende TK [TI [TI, _]]" es realmente posible como genéricos, y cómo. Como se dice que los genéricos y los tipos abstractos son equivalentes en Scala, ¿sabría cómo hacerlo correctamente con los genéricos? –

+0

Lamentablemente, la respuesta corta es que no sé. No estoy seguro de que sean 100% equivalentes. No habría podido convertir para escribir miembros como lo hizo @Miles tampoco. Respuesta larga como una edición. –

+0

No conozco ninguna forma práctica de codificar la recursión que buscas, excepto por el uso de miembros de tipo. Los genéricos y los miembros de tipo no son completamente equivalentes, y esta es una de las situaciones que los diferencia. –

Cuestiones relacionadas