التباين: التغاير والتباين العكسي
أتقن التغاير والتباين العكسي للتعامل بشكل صحيح مع علاقات الأنواع الفرعية في الأنواع العامة
التباين: التغاير والتباين العكسي درس مجاني في Scala for Backend Engineering & Functional Programming على CoddyKit. هذا هو الدرس 2 من أصل 3. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Scala for Backend Engineering & Functional Programming، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Scala for Backend Engineering & Functional Programming 3 دروس في المجموع.
بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.
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!
الأسئلة الشائعة
هل درس «التباين: التغاير والتباين العكسي» مجاني؟
نعم — نص درس «التباين: التغاير والتباين العكسي» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Scala for Backend Engineering & Functional Programming، انتقل إلى CoddyKit PRO. تتضمن دورة Scala for Backend Engineering & Functional Programming 3 دروس في المجموع.
ماذا ستتعلم في «التباين: التغاير والتباين العكسي»؟
أتقن التغاير والتباين العكسي للتعامل بشكل صحيح مع علاقات الأنواع الفرعية في الأنواع العامة تتمرن على Scala for Backend Engineering & Functional Programming مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Scala for Backend Engineering & Functional Programming؟
لا تُشترط خبرة سابقة. Scala for Backend Engineering & Functional Programming على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 3.
كم من الوقت يستغرق درس «التباين: التغاير والتباين العكسي»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Scala for Backend Engineering & Functional Programming هذا؟
نعم. كل درس في Scala for Backend Engineering & Functional Programming يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- الأنواع العامة ومعاملات الأنواع
- التباين: التغاير والتباين العكسي
- فئات الأنواع وImplicits