type parameter implicitly classes function scala covariance contravariance

function - parameter - scala type



¿Por qué la función[-A1,...,+B] no trata de permitir ningún supertipo como parámetro? (4)

Aquí hay dos ideas separadas en acción. Uno está usando la subtipificación para permitir que se pasen argumentos más específicos a una función (llamada subsunción ). El otro es cómo verificar la subtipificación en las funciones mismas.

Para verificar los tipos de argumentos a una función, solo debe verificar que los argumentos dados sean subtipos de los tipos de argumentos declarados. El resultado también tiene que ser un subtipo del tipo declarado. Aquí es donde realmente verificas la subtipificación.

La contra-co-varianza de los parámetros y el resultado solo tienen en cuenta cuando se quiere verificar si un tipo de función dado es un subtipo de otro tipo de función. Entonces, si un parámetro tiene el tipo Function[A1, ... ,B] , entonces el argumento tiene que ser un tipo de función Function[C1, ..., D] donde A1 <: C1 ... y D <: B

Este razonamiento no es específico de Scala y se aplica a otros lenguajes de tipo estático con subtipificación.

Creo que se puede definir la covarianza (al menos para los objetos) como "la capacidad de usar un valor de un (sub) tipo más estrecho en lugar de un valor de algún tipo (super) más amplio", y esa contravarianza es exactamente lo contrario de esta.

Aparentemente, las funciones de Scala son instancias de la Función [-A1, ..., + B] para los tipos de parámetros contravariantes A1, etc. y el tipo de retorno covariante, B. Si bien esto es útil para subtipificar en Funciones, ¿no debería significar la definición anterior? Puedo pasar cualquier supertipo como parámetros?

Por favor avíseme en qué me equivoco.


Covariante significa convertir de más ancho (super) a más estrecho (sub). Por ejemplo, tenemos dos clases: una es animal (súper) y la otra es cat, luego usa covariante, podemos convertir animal en cat.

Contra-variante es todo lo contrario de covariante, lo que significa gato a animal.

Invariante significa que no puede convertir.


Esta pregunta es antigua, pero creo que una explicación más clara es invocar el Principio de sustitución de Liskov: todo lo que es cierto acerca de una superclase debería ser cierto para todas sus subclases. Debería poder hacer con un SubFoo todo lo que puede hacer con un Foo, y tal vez más.

Supongamos que tenemos Calico <: Cat <: Animal y Husky <: Dog <: Animal. Miremos una Function[Cat, Dog] . ¿Qué afirmaciones son ciertas sobre esto? Hay dos:

(1) Puede pasar cualquier Cat (así que cualquier subclase de Cat)

(2) Puede llamar a cualquier método Dog en el valor devuelto

Entonces, ¿tiene sentido la Function[Calico, Dog] <: Function[Cat, Dog] ? No, las afirmaciones que son verdaderas de la superclase no son verdaderas de la subclase, es decir, la declaración (1). No puedes pasar ningún gato a una función que solo tome gatos Calico.

¿Pero tiene sentido la Function[Animal, Dog] <: Function[Cat, Dog] ? Sí, todas las afirmaciones sobre la superclase son verdaderas de la subclase. Todavía puedo pasar a cualquier gato; de hecho, puedo hacer aún más que eso, puedo pasar a cualquier animal, y puedo llamar a todos los métodos de perro en el valor devuelto.

Entonces A <: B implica la Function[B, _] <: Function[A, _]

Ahora, ¿tiene sentido la Function[Cat, Husky] <: Function[Cat, Dog] ? Sí, todas las afirmaciones sobre la superclase son verdaderas de la subclase; Todavía puedo pasar un Cat, y todavía puedo llamar a todos los métodos Dog en el valor devuelto; de hecho, puedo hacer incluso más que eso, puedo llamar a todos los métodos de Husky sobre el valor devuelto.

Pero, ¿ Function[Cat, Animal] <: Function[Cat, Dog] ? No, las afirmaciones que son verdaderas de la superclase no son verdaderas de la subclase, es decir, la declaración (2). No puedo llamar a todos los métodos disponibles en Dog en el valor devuelto, solo los disponibles en Animal.

Entonces con una Function[Animal, Husky] puedo hacer todo lo que puedo hacer con una Function[Cat, Dog] : puedo pasar cualquier Cat, y puedo llamar a todos los métodos de Dog en el valor devuelto. Y puedo hacer aún más: puedo pasar otros animales, y puedo llamar a los métodos disponibles en Husky que no están disponibles en Dog. Entonces tiene sentido: Function[Animal, Husky] <: Function[Cat, Dog] . El primer parámetro de tipo puede ser reemplazado por una superclase, el segundo con una subclase.


La covarianza y la contravarianza son cualidades de la clase, no cualidades de los parámetros . (Son cualidades que dependen de los parámetros, pero hacen afirmaciones sobre la clase).

Entonces, Function1[-A,+B] significa que una función que toma superclases de A se puede ver como una subclase de la función original .

Veamos esto en la práctica:

class A class B extends A val printB: B => Unit = { b => println("Blah blah") } val printA: A => Unit = { a => println("Blah blah blah") }

Ahora suponga que necesita una función que sepa cómo imprimir una B :

def needsB(f: B => Unit, b: B) = f(b)

Podría pasar en printB . Pero también podría pasar en printA , ya que también sabe cómo imprimir B s (¡y más!), Como si A => Unit fuera una subclase de B => Unit . Esto es exactamente lo que significa la contravarianza. ¡No significa que puede pasar Option[Double] a printB y obtener cualquier cosa que no sea un error en tiempo de compilación!

(La covarianza es el otro caso: M[B] <: M[A] si B <: A ).