Scala – Variances

Variance định nghĩa quan hệ kế thừa của các kiểu được tham số hoá (Parameterized Types). Variance là tất cả về sub-typing.

Nào hãy xem ảnh dưới đây để hiểu rõ hơn Type Parameter là gì.

Ở đây, T có thể hiểu là Type Parameter và List[T] được hiểu như là Generic

Ví dụ List[T], nó có thể là List[Int], List[AnyVal]… thì những List[Int], List[AnyVal] được biết như là Parameterized Types

Variance định nghĩa quan hệ kế thừa giữa các kiểu được tham số hoá (Parameterized Types) như ở trên

Ưu điểm của Variance

Ưu điểm chính của Variance trong Scala:

  • Variance giúp cho Collection của Scala trở nên Type-Safe hơn
  • Variance giúp linh động hơn khi phát triển
  • Variace mang đến cho chúng ta một kỹ thuật giúp phát triển các ứng dụng đáng tin cậy

Các kiểu của Variance

Scala hỗ trợ 3 kiểu Variance:

  • Covariant
  • Invariant
  • Contravariant

Covariant

Nếu S là subtype của T thì List[S] là subtype của List[T]

Mối quan hệ kiểu này giữa 2 Parameterized Types được biết như là Convariant

Cú pháp Covariance

Để miêu tả mối quan hệ của Covariance giữa 2 Parameterized Types, Scala sử dụng cú pháp sau đây:

Dấu + được thêm đằng trước kiểu tham số dùng để định nghĩa Covariance trong Scala

T ở đây là kiểu tham số, còn dấu + định nghĩa Covariance của Scala

Chú ý rằng để đơn giản nên tôi sử dụng List ở đây trong ví dụ. Tuy nhiên nó có thể là bất cứ kiểu gì trong Scala như là Set[+T], Ordered[+T] …

Ví dụ

Sau đây là một ví dụ về sử dụng Covariance trong Scala.

class Animal[+T](val animial:T)

class Dog
class Puppy extends Dog

class AnimalCarer(val dog:Animal[Dog])

object ScalaCovarianceTest{
  def main(args: Array[String]) {
    val puppy = new Puppy
    val dog = new Dog

    val puppyAnimal:Animal[Puppy] = new Animal[Puppy](puppy)
    val dogAnimal:Animal[Dog] = new Animal[Dog](dog)

    val dogCarer = new AnimalCarer(dogAnimal)
    val puppyCarer = new AnimalCarer(puppyAnimal)

    println("Done.")
  }
}

Animal class được định nghĩa sử dụng Variance Annotation như ở trên là +T, do đó chúng ta có thể truyền vào dogAnimal hoặc subtype của nó puppyAnimal để tạo một AnimalCarer object.

Nếu chúng ta bỏ Variance Annotation khi định nghĩa class Animal như bên dưới đây.

class Animal[T](val animial:T)
// Đoạn Code còn lại sẻ được giữ nguyên như ở trên.

Nó sẽ không được biên dịch. Chúng ta sẽ nhận được thông báo lỗi như bên dưới đây.

Type mismatch, expected: Animal[Dog], found: Animal[Puppy]

Để giải quyết lỗi bên trên thì chúng ta phải sử dụng Covience của Scala.

Chúng ta có thể giải thích lại ví dụ theo Covience của Scala như sau

Puppy là một loại của Dog, Animal[Puppy] là một loại của Animal[Dog]. Chúng ta có thể sử dụng Animal[Puppy] ở bất cứ chỗ nào chúng ta cần sử dụng Animal[Dog]. Điều này được hiểu là Covience trong Scala.

Contravariant

Nếu S là subtype của T thì List[T] là subtype của List[S]

Mối quan hệ kiểu này giữa 2 Parameterized Types được biết như là Contravariant

Cú pháp Contravariance

Để miêu tả mối quan hệ của Contravariance giữa 2 Parameterized Types, Scala sử dụng cú pháp sau đây:

Dấu – được thêm đằng trước kiểu tham số dùng để định nghĩa Contravariance trong Scala

Ví dụ

Sau đây là một ví dụ về sử dụng Contravariance trong Scala.

abstract class Type [-T]{
  def typeName : Unit
}

class SuperType extends Type[AnyVal]{
  override def typeName: Unit = {
    println("SuperType")
  }
}
class SubType extends Type[Int]{
  override def typeName: Unit = {
    println("SubType")
  }
}

class TypeCarer{
  def display(t: Type[Int]){
    t.typeName
  }
}

object ScalaContravarianceTest {

  def main(args: Array[String]) {
    val superType = new SuperType
    val subType = new SubType

    val typeCarer = new TypeCarer

    typeCarer.display(subType)
    typeCarer.display(superType)
  }

}

Như chúng ta định nghĩa Contravariance trong Type[-T], đoạn code trên chạy tốt như mong muốn. TypeCarer.display() được định nghĩa với Type[Int] ví dụ ở trên là Subtype, nhưng nó vẫn chấp nhận Type[AnyVal] bởi vì chúng ta sử dụng Contravariance

Nếu chúng ta bỏ dấu “-” thành Type[T], thì chúng ta sẽ gặp lỗi khi biên dịch.

Invariant

Nếu S là subtype của T thì List[S] và List[T] sẽ không có quan hệ kế thừa hay sub-typing. Nghĩa là cả 2 đều không có quan hệ gì với nhau.

Kiểu quan hệ này giữa 2 Parameterized Types được biết như là Invariant hay Non-Variant

Trong Scala, mặc định Generic Type là Non-Variant, không có quan hệ kế thừa. Nếu chúng ta định nghĩa Parameterized Types không sử dụng dấu “+” và “-“, thì chúng được hiểu là Invariant.

Variance Annotation là gì?

Variance Annotation nghĩa là định nghĩa dấu “+” hoặc “-” trước Type Parameter.

Ví dụ +T và -T được biết như là Variance Annotation trong Scala

Kết luận

Chúng ta có thể tóm tắt lại cả 3 khái niệm trên như sau

Kiểu Variance Cú pháp Miêu tả
Covariant [+T] Nếu S là subtype của T, thì List[S] cũng là subtype của List[T]
Contravariant [-T] Nếu S là subtype của T, thì List[T] cũng là subtype của List[S]
Invariant [T] Nếu S là subtype của T, thì List[S] và List[T] không có liên hệ

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.