java - software - ¿No es el tipo de argumento co-no contra-variante?
scala tutorial (4)
Así es como se definen las funciones en Scala :
trait Function1 [-T1, +R] extends AnyRef
En inglés, el parámetro T1
es contravariante y el tipo de resultado R
es covariante. Qué significa eso?
Cuando una parte del código requiere una función de tipo Dog => Animal
, puede proporcionar una función de tipo Animal => Animal
, gracias a la contravarianza del parámetro (puede usar un tipo más amplio).
También puede proporcionar la función del tipo Dog => Dog
, gracias a la covarianza del tipo de resultado (puede usar el tipo más estrecho).
Esto realmente tiene sentido: alguien quiere una función para transformar el perro en cualquier animal. Puede suministrar una función que transforme cualquier animal (incluidos los perros). También su función puede devolver solo perros, pero los perros siguen siendo animales.
Entiendo los términos covarianza y contravarianza. Pero hay una pequeña cosa que no puedo entender. En el curso "Programación funcional en Scala" en Coursera, Martin Ordersky menciona que:
Las funciones son contravariantes en sus tipos de argumentos y covariantes en sus tipos de retorno.
Así, por ejemplo, en Java, dejemos que Dog
amplíe Animal
. Y dejar que una función sea:
void getSomething(Animal a){
y tengo la función de llamada como
Dog d = new Dog();
getSomething(d)
Así que básicamente lo que está pasando es que Animal a = d
. Y de acuerdo con wiki covarianza es "Convertir de ancho a estrecho". Y encima estamos convirtiendo de perro a animal. ¿Entonces el tipo de argumento no es covariante en lugar de contravariante?
Convertir Dog
en Animal
está convirtiendo de estrecho a más ancho, por lo que no es covarianza.
Creo que la pregunta original sobre la conversión de Perro a Animal ya se ha aclarado, pero podría ser interesante observar que existe una razón por la cual las funciones se definen como contravariantes en sus argumentos y como covariantes en sus tipos de devolución. Digamos que tienes dos funciones:
val f: Vertebrate => Mammal = ??? val g: Mammal => Primate = ???
Como estamos hablando de funciones, usted esperaría que la composición de funciones esté entre sus operaciones primitivas. De hecho, puede componer f y g ( gof ) y obtener como resultado una función:
val h: Vertebrate => Primate = f andThen g
Pero puedo reemplazar g
con un subtipo:
val gChild: Animal => Primate
Sin romper la composabilidad. Y gChild
es un subtipo de g
precisamente porque definimos la función contravariante en su argumento. Como conclusión, puede ver que una función debe definirse de tal manera si desea capturar y preservar la idea de la capacidad de composición de las funciones . Puede encontrar más detalles y algunos gráficos que deberían ayudar a digerir este tema here
Recuerdo que me sentí confundido por esa frase cuando estaba leyendo el Scala Book en 2007. Martin lo dice como si estuviera hablando de una característica del idioma, pero en esa oración solo menciona un hecho sobre las funciones en general. Scala, específicamente, modela ese hecho simplemente por un rasgo regular. Dado que Scala tiene una variación de sitio de declaración, expresar esas semánticas es natural para el lenguaje.
Java Generics, por otro lado, admite solo la varianza del sitio de uso, por lo que la más cercana a la contravarianza de un tipo de función en Java es codificarlo manualmente en cada sitio de uso:
public int secondOrderFunction(Function<? super Integer, ? extends Number> fn) {
....
}
(Suponiendo una Function<P, R>
interfaz debidamente declarada Function<P, R>
, P
representa el tipo de parámetro y R
para el tipo de retorno). Naturalmente, dado que este código está en manos del cliente, y no siendo en absoluto específico de las funciones, la declaración sobre la varianza del tipo de parámetro / retorno no es aplicable a ninguna característica de lenguaje de Java. Solo es aplicable en un sentido más amplio, perteneciente a la naturaleza de las funciones.
Java 8 introducirá cierres, lo que implica funciones de primera clase, pero, según el comentario de Jörg a continuación, la implementación no incluirá un tipo de función de pleno derecho.