Varianza: covarianza e controvarianza
Padroneggi covarianza e controvarianza per gestire correttamente le relazioni di sottotipizzazione nei tipi generici.
Varianza: covarianza e controvarianza è una lezione Scala for Backend Engineering & Functional Programming gratuita su CoddyKit. Questa è la lezione 2 di 3. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Scala for Backend Engineering & Functional Programming, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.
Che cos'è la varianza dei tipi?
In Scala, quando si dispone di una gerarchia di tipi (ad esempio, Cat è un sottotipo di Animal), come si comportano i tipi generici?
List[Cat] è considerato un sottotipo di List[Animal]? Non sempre, per impostazione predefinita!
La varianza dei tipi consente di definire come vengono mantenute (o invertite) le relazioni di sottotipizzazione per i tipi generici. È fondamentale per scrivere codice flessibile e sicuro dal punto di vista dei tipi.
Covarianza: tipi "produttori" (+T)
La covarianza è indicata inserendo un + prima del parametro di tipo (ad esempio, trait Box[+T]).
- Se
Aè un sottotipo diB, alloraBox[A]diventa un sottotipo diBox[B]. - Consideri i tipi covarianti come "produttori" di
T. Possono comparire solo in posizioni di output (come i tipi restituiti dai metodi). - Ciò significa che, se è previsto un
Box[Animal], è possibile fornire unBox[Cat]perché "produce" qualcosa di più specifico (unCatè unAnimal).
La covarianza in azione
Provi a eseguire questo esempio di trait Producer covariante:
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}")
}
}Quando utilizzare la covarianza
La covarianza è sicura quando il tipo generico "produce" solo valori di tipo T o non accetta mai T come argomento.
- Le collezioni immutabili come
List[+T]ne sono un esempio fondamentale. È possibile trattare unaList[Cat]come unaList[Animal]perché da essa si "leggono" soltantoAnimal(o i relativi sottotipi). - Non è possibile aggiungere un
Animalqualsiasi a unaList[Cat]se questa è tipizzata comeList[Animal], mantenendo così la sicurezza dei tipi.
Controvarianza: tipi "consumatori" (-T)
La controvarianza è indicata inserendo un - prima del parametro di tipo (ad esempio, trait Consumer[-T]).
- Se
Aè un sottotipo diB, alloraConsumer[B]diventa un sottotipo diConsumer[A]. - Consideri i tipi controvarianti come "consumatori" di
T. Possono comparire solo in posizioni di input (come i tipi dei parametri dei metodi). - Ciò significa che, se è previsto un
Consumer[Cat], è possibile fornire unConsumer[Animal]perché "consuma" qualcosa di più generale (può gestire qualsiasiAnimal, incluso unCat).
La controvarianza in azione
Provi a eseguire questo esempio di trait Consumer controvariante:
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.")
}
}Quando utilizzare la controvarianza
La controvarianza è sicura quando il tipo generico "consuma" solo valori di tipo T o non restituisce mai T.
- Un esempio comune sono le funzioni, in particolare il tipo del parametro di input. Se una funzione può elaborare qualsiasi
Animal(Animal => Unit), può certamente elaborare unCat. Pertanto,(Animal => Unit)è un sottotipo di(Cat => Unit). - Ciò offre maggiore flessibilità quando si passano funzioni come argomenti.
Invarianza: il comportamento predefinito
Se non specifica + o -, il parametro di tipo è invariante. Questo è il comportamento predefinito in Scala.
Box[A]è un sottotipo diBox[B]solo seAè esattamente dello stesso tipo diB.- Ciò è spesso necessario per le collezioni mutabili (ad esempio,
Array[T]) per prevenire problemi di sicurezza dei tipi, poiché si potrebbero sia leggere sia scrivere sottotipi diversi.
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.")
}
}Funzioni: covarianti e controvarianti
I tipi funzione di Scala, Function1[-A, +B], combinano elegantemente entrambi i tipi di varianza:
- Il parametro di input
Aè controvariante (-A). Ciò significa che una funzione che accetta un tipo più generale (ad esempio,Animal) può essere utilizzata dove è prevista una funzione che accetta un tipo più specifico (ad esempio,Cat). - Il tipo restituito
Bè covariante (+B). Ciò significa che una funzione che restituisce un tipo più specifico (ad esempio,Cat) può essere utilizzata dove è prevista una funzione che restituisce un tipo più generale (ad esempio,Animal).
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}")
}
}Verifica rapida: regole della varianza
Consideri il trait seguente:
trait Handler[T] {
def handle(item: T): Unit
}Per consentire l'utilizzo di Handler[Animal] dove è previsto un Handler[Cat] (dove Cat estende Animal), quale annotazione di varianza dovrebbe avere T?
Riepilogo: padronanza della varianza
Ha acquisito la padronanza della varianza in Scala! Ecco un breve riepilogo:
- Covarianza (
+T): consente aContainer[Subtype]di essere un sottotipo diContainer[Supertype]. È utile per i tipi "produttori" che restituiscono soloT. - Controvarianza (
-T): consente aContainer[Supertype]di essere un sottotipo diContainer[Subtype]. È utile per i tipi "consumatori" che accettano soloTcome input. - Invarianza: è il comportamento predefinito. I tipi devono corrispondere esattamente.
Comprendere la varianza La aiuta a creare codice generico Scala più flessibile e sicuro dal punto di vista dei tipi!
Domande Frequenti
La lezione «Varianza: covarianza e controvarianza» è gratuita?
Sì — il testo completo di «Varianza: covarianza e controvarianza» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Scala for Backend Engineering & Functional Programming, passa a CoddyKit PRO. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.
Cosa imparerò in «Varianza: covarianza e controvarianza»?
Padroneggi covarianza e controvarianza per gestire correttamente le relazioni di sottotipizzazione nei tipi generici. Eserciti Scala for Backend Engineering & Functional Programming con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Scala for Backend Engineering & Functional Programming?
Non è richiesta alcuna esperienza precedente. Scala for Backend Engineering & Functional Programming su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 3.
Quanto tempo richiede la lezione «Varianza: covarianza e controvarianza»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Scala for Backend Engineering & Functional Programming?
Sì. Ogni lezione Scala for Backend Engineering & Functional Programming include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Generics e parametri di tipo
- Varianza: covarianza e controvarianza
- Type class e impliciti