0Pricing
Scala for Backend Engineering & Functional Programming · درس

التباين: التغاير والتباين العكسي

أتقن التغاير والتباين العكسي للتعامل بشكل صحيح مع علاقات الأنواع الفرعية في الأنواع العامة

التباين: التغاير والتباين العكسي درس مجاني في 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 A is a subtype of B, then Box[A] becomes a subtype of Box[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], a Box[Cat] can be provided because it 'produces' something more specific (a Cat is an Animal).

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 a List[Cat] as a List[Animal] because you only ever 'read' Animals (or their subtypes) from it.
  • You cannot add an arbitrary Animal to a List[Cat] if it's typed as List[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 A is a subtype of B, then Consumer[B] becomes a subtype of Consumer[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], a Consumer[Animal] can be provided because it 'consumes' something more general (it can handle any Animal, including a Cat).

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 a Cat. 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 of Box[B] if A is exactly the same type as B.
  • 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 A is 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 B is 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): Allows Container[Subtype] to be a subtype of Container[Supertype]. Useful for 'producer' types that only return T.
  • Contravariance (-T): Allows Container[Supertype] to be a subtype of Container[Subtype]. Useful for 'consumer' types that only accept T as 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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. الأنواع العامة ومعاملات الأنواع
  2. التباين: التغاير والتباين العكسي
  3. فئات الأنواع وImplicits
← العودة إلى Scala for Backend Engineering & Functional Programming