Varianza: covarianza y contravarianza
Domine la covarianza y la contravarianza para gestionar correctamente las relaciones de subtipado en tipos genéricos.
Varianza: covarianza y contravarianza es una lección gratuita de Scala for Backend Engineering & Functional Programming en CoddyKit. Esta es la lección 2 de 3. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Scala for Backend Engineering & Functional Programming, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
What is Type Variance?
In Scala, when you have a type hierarchy (e.g., Cat is a subtype of Animal), how do generic types behave?
Is a List[Cat] considered a subtype of List[Animal]? Not always by default!
Type variance allows us to define how subtyping relationships are preserved (or reversed) for generic types. This is crucial for writing flexible and type-safe code.
Covariance: 'Producer' Types (+T)
Covariance is denoted by placing a + before the type parameter (e.g., trait Box[+T]).
- If
Ais a subtype ofB, thenBox[A]becomes a subtype ofBox[B]. - Think of covariant types as 'producers' of
T. They can only appear in output positions (like return types of methods). - This means if you expect a
Box[Animal], aBox[Cat]can be provided because it 'produces' something more specific (aCatis anAnimal).
Covariance in Action
Try running this example of a covariant Producer trait:
class Animal
class Cat extends Animal
trait Producer[+T] {
def produce: T // Output position
}
class CatProducer extends Producer[Cat] {
def produce: Cat = new Cat
}
object Main {
def main(args: Array[String]): Unit = {
val catProducer: Producer[Cat] = new CatProducer
// Because Producer is covariant, Producer[Cat] is a subtype of Producer[Animal]
val animalProducer: Producer[Animal] = catProducer
println("Assigned CatProducer to AnimalProducer.")
println(s"Produced: ${animalProducer.produce.getClass.getSimpleName}")
}
}When to Use Covariance
Covariance is safe when your generic type only 'produces' values of type T, or never accepts T as an argument.
- Immutable collections like
List[+T]are a prime example. You can treat aList[Cat]as aList[Animal]because you only ever 'read'Animals (or their subtypes) from it. - You cannot add an arbitrary
Animalto aList[Cat]if it's typed asList[Animal], which maintains type safety.
Contravariance: 'Consumer' Types (-T)
Contravariance is denoted by placing a - before the type parameter (e.g., trait Consumer[-T]).
- If
Ais a subtype ofB, thenConsumer[B]becomes a subtype ofConsumer[A]. - Think of contravariant types as 'consumers' of
T. They can only appear in input positions (like parameter types of methods). - This means if you expect a
Consumer[Cat], aConsumer[Animal]can be provided because it 'consumes' something more general (it can handle anyAnimal, including aCat).
Contravariance in Action
Try running this example of a contravariant Consumer trait:
class Animal
class Cat extends Animal
trait Consumer[-T] {
def consume(item: T): Unit // Input position
}
class AnimalConsumer extends Consumer[Animal] {
def consume(item: Animal): Unit = {
println(s"Consumed an animal: ${item.getClass.getSimpleName}")
}
}
object Main {
def main(args: Array[String]): Unit = {
val animalConsumer: Consumer[Animal] = new AnimalConsumer
// Because Consumer is contravariant, Consumer[Animal] is a subtype of Consumer[Cat]
val catConsumer: Consumer[Cat] = animalConsumer
catConsumer.consume(new Cat)
println("Assigned AnimalConsumer to CatConsumer.")
}
}When to Use Contravariance
Contravariance is safe when your generic type only 'consumes' values of type T, or never returns T.
- A common example is functions, specifically the input parameter type. If a function can process any
Animal(Animal => Unit), it can certainly process aCat. So,(Animal => Unit)is a subtype of(Cat => Unit). - This allows for greater flexibility when passing functions as arguments.
Invariance: The Default Behavior
If you don't specify + or -, the type parameter is invariant. This is the default in Scala.
Box[A]is only a subtype ofBox[B]ifAis exactly the same type asB.- This is often necessary for mutable collections (e.g.,
Array[T]) to prevent type safety issues, as you could both read and write different subtypes.
class Food
class Apple extends Food
// Invariant Box
class Box[T](val item: T) {
def getContent: T = item
}
object Main {
def main(args: Array[String]): Unit = {
val appleBox = new Box(new Apple)
// The following line would cause a compile error:
// val foodBox: Box[Food] = appleBox
println(s"An Apple Box contains: ${appleBox.getContent.getClass.getSimpleName}")
println("Box[Apple] is NOT a subtype of Box[Food] (invariant).")
println("The types must match exactly for invariant types.")
}
}Functions: Both Covariant & Contravariant
Scala's function types, Function1[-A, +B], elegantly combine both variance types:
- The input parameter
Ais contravariant (-A). This means a function that accepts a more general type (e.g.,Animal) can be used where a function accepting a more specific type (e.g.,Cat) is expected. - The return type
Bis covariant (+B). This means a function that returns a more specific type (e.g.,Cat) can be used where a function returning a more general type (e.g.,Animal) is expected.
class Vehicle
class Car extends Vehicle
object Main {
def main(args: Array[String]): Unit = {
// Contravariance for input: (Vehicle => Unit) is a subtype of (Car => Unit)
val printVehicle: Vehicle => Unit = (v: Vehicle) => println(s"Printing vehicle: ${v.getClass.getSimpleName}")
val printCar: Car => Unit = printVehicle // OK: A general printer can print a specific car
printCar(new Car)
// Covariance for output: (() => Car) is a subtype of (() => Vehicle)
val getCar: () => Car = () => new Car
val getVehicle: () => Vehicle = getCar // OK: A specific producer can fulfill a general request
println(s"Got vehicle: ${getVehicle().getClass.getSimpleName}")
}
}Quick Check: Variance Rules
Consider the following trait:
trait Handler[T] {
def handle(item: T): Unit
}To allow Handler[Animal] to be used where a Handler[Cat] is expected (where Cat extends Animal), what variance annotation should T have?
Recap: Variance Mastery
You've mastered variance in Scala! Here's a quick recap:
- Covariance (
+T): AllowsContainer[Subtype]to be a subtype ofContainer[Supertype]. Useful for 'producer' types that only returnT. - Contravariance (
-T): AllowsContainer[Supertype]to be a subtype ofContainer[Subtype]. Useful for 'consumer' types that only acceptTas input. - Invariance: The default. Types must match exactly.
Understanding variance helps you create more flexible and type-safe generic code in Scala!
Preguntas frecuentes
¿La lección «Varianza: covarianza y contravarianza» es gratis?
Sí — el texto completo de «Varianza: covarianza y contravarianza» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Scala for Backend Engineering & Functional Programming, actualiza a CoddyKit PRO. El curso de Scala for Backend Engineering & Functional Programming incluye 3 lecciones en total.
¿Qué aprenderé en «Varianza: covarianza y contravarianza»?
Domine la covarianza y la contravarianza para gestionar correctamente las relaciones de subtipado en tipos genéricos. Practicas Scala for Backend Engineering & Functional Programming con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Scala for Backend Engineering & Functional Programming?
No se requiere experiencia previa. Scala for Backend Engineering & Functional Programming en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 3.
¿Cuánto tiempo toma la lección «Varianza: covarianza y contravarianza»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Scala for Backend Engineering & Functional Programming?
Sí. Cada lección de Scala for Backend Engineering & Functional Programming incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Genéricos y parámetros de tipo
- Varianza: covarianza y contravarianza
- Clases de tipos e implicits