2010-09-12 9 views
7

Por favor, eche un vistazo al código siguiente, donde Extractor[A,B] es parte de un marco genérico y todo lo demás debe considerarse como "código de cliente" (hermé un poco y renombrado todo. Así que no importa que Extractor no parezca demasiado útil).Herencia y conversión de tipo automático?

scala> abstract class Extractor[A,B] {           
    | def extract(d:A):B              
    | def stringRepr(d:A):String            
    | }                   
defined class Extractor 

scala> sealed abstract class Value            
defined class Value 

scala> case class IntValue(i:Int) extends Value         
defined class IntValue 

scala> case class StringValue(s:String) extends Value       
defined class StringValue 

scala> case class Data(i:Int, s:String)           
defined class Data 

scala> sealed abstract class MyExtractor[Value] extends Extractor[Data, Value] { 
    | def stringRepr(d:Data) = extract(d) match {        
    |  case IntValue(i) => i.toString          
    |  case StringValue(s) => s            
    | }                  
    | }                   
defined class MyExtractor 

scala> class IntExtractor(name:String) extends MyExtractor[IntValue] { 
    | def extract(d:Data) = IntValue(d.i) 
    | } 
defined class IntExtractor 

scala> class StringExtractor(name:String) extends MyExtractor[StringValue] { 
    | def extract(d:Data) = StringValue(d.s) 
    | } 
defined class StringExtractor 

por lo que en palabras cortas Extractor[A,B] se utiliza para extraer algún valor B de A y hacer algunas otras cosas que no están representados en este código espectáculo. Las clases abstractas Value y MyExtractor se utilizan por razones de tipo savety en el "código de cliente". Cuando intento crear un List de MyExtractor s, ocurre lo siguiente:

scala> val l = List.empty[MyExtractor[Value]] 
l: List[MyExtractor[Value]] = List() 

scala> new IntExtractor("test1") :: l 
res5: List[MyExtractor[_ >: IntValue <: Value]] = List([email protected]) 

tratando de convertir un IntExractor a una superclase

scala> new IntExtractor("test"):MyExtractor[Value] 
<console>:24: error: type mismatch; 
found : IntExtractor 
required: MyExtractor[Value] 
     new IntExtractor("test"):MyExtractor[Value] 
    ^

scala> new IntExtractor("test"):Extractor[Data,Value] 
<console>:24: error: type mismatch; 
found : IntExtractor 
required: Extractor[Data,Value] 
     new IntExtractor("test"):Extractor[Data,Value] 

Soy consciente de que todo está bien, cuando me defino IntExtractor como este

scala> class IntExtractor(name:String) extends MyExtractor[Value] { 
    | def extract(d:Data) = IntValue(d.i)        
    | } 
defined class IntExtractor 

scala> new IntExtractor("test"):Extractor[Data,Value]    
res17: Extractor[Data,Value] = [email protected] 

Pero no entiendo, ¿por qué no funciona de la manera en que lo intenté arriba. Les agradecería cualquier ayuda o sugerencia.

+6

Como regla general, es útil no utilizar los nombres de las clases reales para los parámetros de tipo. Por ejemplo, en 'class MyExtractor [Value]' the 'Value' es un parámetro de tipo y no tiene nada que ver con su' class Value'. Este uso de los nombres tiende a ser confuso para los humanos, aunque por supuesto nunca desconcierta al compilador. –

+0

Su 'Extractor [A, B]' se comporta como 'A => B' (es decir, 'Función1'). Tenga en cuenta que 'Function1' es covariante en su tipo de retorno; se declara como' Function1 [-A, + B] ' –

+0

@ Randall Schulz: No estoy 100% seguro. Lo entiendo bien, pero el' Value' in 'clase MyExtractor [Value] 'SÍ corresponde con la' clase Value'. Por ejemplo, solo estoy buscando subclases de 'Value' en' stringRepr (d: Value): String' – Agl

Respuesta

7

Por lo que yo sé, el concepto que está buscando es "covarianza". El hecho de que IntValue sea un subtipo de Value no significa que MyExtractor[IntValue] sea un subtipo de MyExtractor[Value]. De manera predeterminada, no hay ninguna relación de subtipado entre esos dos tipos. Para crear dicha relación, debe declarar MyExtractor como covariante con respecto a su parámetro. Scala le permite declarar que los parámetros de tipo son covariantes agregando un "+" antes de la declaración de parámetros de tipo. Esto se llama notación de varianza.

sealed abstract class MyExtractor[+Value] extends Extractor[Data, Value] {   
} 

Scala también es compatible con la contravariancia sobre los parámetros de tipo. La contradicción es como la covarianza, pero se invierte, y se expresa con una notación de varianza "-" en el parámetro de tipo. Su tipo Extractor proporciona un excelente ejemplo de un lugar donde una notación de contravarianza tiene sentido.

abstract class Extractor[-A,+B] {           
    def extract(d:A):B              
    def stringRepr(d:A):String            
}  

Esto significa que si Foo es un subtipo de Bar, entonces Extractor[Bar, Baz] es un subtipo de Extractor[Foo, Baz], que si se piensa en que tiene sentido. Si algo puede extraer los datos que desea al pasar una instancia de un supertipo, entonces, por definición, puede extraerlo al pasar una instancia de un subtipo. Por el contrario, si Foo es un subtipo de Bar, entonces Extractor[Baz, Foo] es un subtipo de Extractor[Baz, Bar]. Eso también tiene sentido. Si tiene un extractor que devuelve un Foo, puede usarlo donde sea que necesite un extractor que devuelve Bar.

Existen restricciones sobre cuándo se pueden declarar la contradicción y la covarianza. Por ejemplo, los parámetros de tipo contravariante solo se pueden usar como argumentos de método, y los parámetros covariantes solo se pueden usar como valores de retorno o valores de método. Ninguno de los dos puede usarse como vars. Se vuelve más complicado con los parámetros de tipo anidados, pero las reglas básicamente se reducen a "donde es razonable", y tu ejemplo cumple con todos ellos.

Nota adicional, todas sus clases abstractas en su ejemplo probablemente deberían declararse como rasgos. Siempre que sus clases abstractas no requieran argumentos de constructor, declararlas como rasgos le da unas pocas oportunidades más para su reutilización.

+0

¡Muy buena explicación! – Landei

+0

¡guau! gracias por tu respuesta, Dave. Para resumir: cambiaste todo lo que dijiste y funciona. ¡Gracias! Para hacerlo un poco más grande: ahora que lo señalaste es realmente obvio (incluso las restricciones) aunque aún no puedo entender cómo funciona la contravariación. . . Todavía tengo algo que pensar;) Utilicé clases abstractas ya que no sabía, que los rasgos podrían tener el modificador 'sellado'. Pero entonces de nuevo . . . ¿Por qué no? ;) Muchas gracias – Agl

Cuestiones relacionadas