2011-12-26 34 views
26

Tôi hiểu hiệp phương sai và contravariance trong scala. Hiệp phương sai có nhiều ứng dụng trong thế giới thực, nhưng tôi không thể nghĩ ra bất kỳ ứng dụng nào cho các ứng dụng contravariance, ngoại trừ các ví dụ cũ cho các hàm.Scala contravariance - ví dụ thực tế đời sống

Ai đó có thể làm sáng tỏ trên ví dụ thế giới thực của contravariance sử dụng?

+0

Xem câu trả lời của Daniel Spiewak cho http: // stackoverflow.com/questions/663254/scala-hiệp phương sai-contravariance-câu hỏi – sourcedelica

+3

... bởi vì không ai sử dụng các chức năng trong thế giới thực? =) –

Trả lời

20

Theo tôi, hai ví dụ đơn giản nhất sau khi Function được đặt hàng và bình đẳng. Tuy nhiên, thứ nhất không phải là biến thể trong thư viện chuẩn của Scala, và thứ hai thậm chí không tồn tại trong nó. Vì vậy, tôi sẽ sử dụng tương đương Scalaz: OrderEqual.

Tiếp theo, tôi cần một số cấu trúc phân cấp lớp, tốt nhất là một cấu trúc quen thuộc và dĩ nhiên, cả hai khái niệm trên đều phải có ý nghĩa đối với nó. Nếu Scala có một lớp bậc trên Number của tất cả các loại số, điều đó sẽ hoàn hảo. Thật không may, nó không có điều như vậy.

Vì vậy, tôi sẽ cố gắng tạo các ví dụ với các bộ sưu tập. Để đơn giản, hãy xem xét Seq[Int]List[Int]. Phải rõ ràng rằng List[Int] là một loại phụ của Seq[Int], tức là, List[Int] <: Seq[Int].

Vì vậy, chúng tôi có thể làm gì với ứng dụng này? Đầu tiên, chúng ta hãy viết một cái gì đó so sánh hai danh sách:

def smaller(a: List[Int], b: List[Int])(implicit ord: Order[List[Int]]) = 
    if (ord.order(a,b) == LT) a else b 

Bây giờ tôi sẽ viết một ngầm Order cho Seq[Int]:

implicit val seqOrder = new Order[Seq[Int]] { 
    def order(a: Seq[Int], b: Seq[Int]) = 
    if (a.size < b.size) LT 
    else if (b.size < a.size) GT 
    else EQ 
} 

Với những định nghĩa này, bây giờ tôi có thể làm một cái gì đó như thế này:

scala> smaller(List(1), List(1, 2, 3)) 
res0: List[Int] = List(1) 

Lưu ý rằng tôi yêu cầu Order[List[Int]], nhưng tôi đang chuyển một số Order[Seq[Int]]. Điều này có nghĩa là Order[Seq[Int]] <: Order[List[Int]]. Cho rằng Seq[Int] >: List[Int], điều này chỉ có thể vì contra-variance.

Câu hỏi tiếp theo là: nó có ý nghĩa gì không?

Hãy xem xét lại smaller. Tôi muốn so sánh hai danh sách các số nguyên. Đương nhiên, bất cứ điều gì so sánh hai danh sách là chấp nhận được, nhưng logic của cái gì đó so sánh hai Seq[Int] là chấp nhận được?

Lưu ý trong định nghĩa của seqOrder cách những thứ được so sánh sẽ trở thành thông số cho nó. Rõ ràng, một List[Int] có thể là một tham số cho một cái gì đó mong đợi một Seq[Int]. Từ đó sau đó một cái gì đó mà một cái gì đó so sánh Seq[Int] là chấp nhận được ở vị trí của một cái gì đó so sánh List[Int]: cả hai đều có thể được sử dụng với các thông số tương tự.

Điều gì ngược lại? Giả sử tôi có phương pháp chỉ so sánh :: (khuyết điểm của danh sách), cùng với Nil, là loại phụ của List. Tôi rõ ràng không thể sử dụng điều này, bởi vì smaller cũng có thể nhận được Nil để so sánh. Không thể sử dụng Order[::[Int]] thay vì Order[List[Int]].

Hãy tiến tới bình đẳng, và viết một phương pháp cho nó:

def equalLists(a: List[Int], b: List[Int])(implicit eq: Equal[List[Int]]) = eq.equal(a, b) 

Order kéo dài Equal, tôi có thể sử dụng nó với cùng ngầm trên:

scala> equalLists(List(4, 5, 6), List(1, 2, 3)) // we are comparing lengths! 
res3: Boolean = true 

Logic ở đây là cùng một. Bất cứ điều gì có thể cho biết hai Seq[Int] là cùng một có thể, rõ ràng, cũng cho biết liệu hai List[Int] là như nhau. Từ đó, nó theo sau là Equal[Seq[Int]] <: Equal[List[Int]], điều này đúng bởi vì Equal là một biến thể.

+0

Có scala.Equiv, nhưng nó không phải là contravariant hoặc. – extempore

+0

Trong ví dụ "nhỏ hơn", tôi có thể làm: 'def nhỏ hơn (a: List [Int], b: List [Int]) (mệnh lệnh ngầm định: Thứ tự [Seq [Int]]) = if (ord. thứ tự (a, b) == LT) a else b'? thay vì 'Order [List [Int]]' và đạt được mục đích tương tự? Tại sao sử dụng contravariance? – Chao

+0

@Chao Có, bạn có thể, nhưng sau đó bạn sẽ không nhận được một thứ tự cụ thể hơn. –

18

Ví dụ này là từ dự án cuối cùng mà tôi đang thực hiện. Giả sử bạn có loại lớp PrettyPrinter[A] cung cấp logic cho các đối tượng in đẹp thuộc loại A. Bây giờ nếu B >: A (ví dụ: nếu B là siêu lớp của A) và bạn biết cách in đẹp B (ví dụ: có một phiên bản PrettyPrinter[B] có sẵn) thì bạn có thể sử dụng cùng một logic để in đẹp A. Nói cách khác, B >: A ngụ ý PrettyPrinter[B] <: PrettyPrinter[A]. Vì vậy, bạn có thể khai báo PrettyPrinter[A] contravariant trên A.

scala> trait Animal 
defined trait Animal 

scala> case class Dog(name: String) extends Animal 
defined class Dog 

scala> trait PrettyPrinter[-A] { 
    | def pprint(a: A): String 
    | } 
defined trait PrettyPrinter 

scala> def pprint[A](a: A)(implicit p: PrettyPrinter[A]) = p.pprint(a) 
pprint: [A](a: A)(implicit p: PrettyPrinter[A])String 

scala> implicit object AnimalPrettyPrinter extends PrettyPrinter[Animal] { 
    | def pprint(a: Animal) = "[Animal : %s]" format (a) 
    | } 
defined module AnimalPrettyPrinter 

scala> pprint(Dog("Tom")) 
res159: String = [Animal : Dog(Tom)] 

Một số ví dụ khác sẽ là Ordering loại hạng từ thư viện chuẩn Scala, Equal, Show (đẳng cấu với PrettyPrinter ở trên), Resource kiểu lớp từ Scalaz, vv

Edit:
Như Daniel đã chỉ ra, Scala's Ordering không phải là contravariant. (Tôi thực sự không biết tại sao.) Thay vào đó, bạn có thể xem xét scalaz.Order được thiết kế cho cùng một mục đích như scala.Ordering nhưng có thể bị thay đổi về thông số loại của nó.

Hợp đồng bổ sung:
Mối quan hệ siêu loại phụ là một loại mối quan hệ có thể tồn tại giữa hai loại. Có thể có nhiều mối quan hệ như vậy có thể. Hãy xem xét hai loại AB có liên quan với hàm f: B => A (nghĩa là mối quan hệ tùy ý). Kiểu dữ liệu F[_] được cho là một hàm thay thế nếu bạn có thể xác định một phép toán contramap cho nó có thể nâng một hàm kiểu B => A thành F[A => B].

Các luật sau đây cần phải được thỏa mãn:

  1. x.contramap(identity) == x
  2. x.contramap(f).contramap(g) == x.contramap(f compose g)

Tất cả các kiểu dữ liệu thảo luận ở trên (Show, Equal vv) là contuncariant functors. Thuộc tính này cho phép chúng ta làm những việc hữu ích như một minh họa dưới đây:

Giả sử bạn có một lớp Candidate định nghĩa là:

case class Candidate(name: String, age: Int) 

Bạn cần một Order[Candidate] mà ra lệnh cho các ứng cử viên theo độ tuổi của họ. Bây giờ bạn biết rằng có một phiên bản Order[Int] có sẵn.Bạn có thể lấy một ví dụ Order[Candidate] từ đó với những hoạt động contramap:

val byAgeOrder: Order[Candidate] = 
    implicitly[Order[Int]] contramap ((_: Candidate).age) 
2

Ví dụ dựa trên hệ thống phần mềm theo hướng sự kiện trong thế giới thực. Một hệ thống như vậy dựa trên các loại sự kiện rộng lớn, như các sự kiện liên quan đến hoạt động của hệ thống (các sự kiện hệ thống), các sự kiện được tạo ra bởi các hành động của người dùng (các sự kiện của người dùng) và vân vân.

Một hệ thống phân cấp sự kiện thể:

trait Event 

trait UserEvent extends Event 

trait SystemEvent extends Event 

trait ApplicationEvent extends SystemEvent 

trait ErrorEvent extends ApplicationEvent 

Bây giờ các lập trình viên làm việc trên các hệ thống hướng sự kiện cần phải tìm một cách để đăng ký/quy trình các sự kiện được tạo ra trong hệ thống. Họ sẽ tạo ra một đặc điểm, Sink, được sử dụng để đánh dấu các thành phần cần được thông báo khi một sự kiện đã được kích hoạt.

trait Sink[-In] { 
    def notify(o: In) 
} 

Kết quả của việc đánh dấu tham số kiểu với ký hiệu -, loại Sink trở thành contravariant.

Một cách có thể để thông báo cho các bên quan tâm rằng một sự kiện đã xảy ra là viết một phương pháp và chuyển cho nó sự kiện tương ứng. Phương pháp này sẽ giả định xử lý một số và sau đó nó sẽ chăm sóc thông báo cho sự kiện chìm:

def appEventFired(e: ApplicationEvent, s: Sink[ApplicationEvent]): Unit = { 
    // do some processing related to the event 
    // notify the event sink 
    s.notify(e) 
} 

def errorEventFired(e: ErrorEvent, s: Sink[ErrorEvent]): Unit = { 
    // do some processing related to the event 
    // notify the event sink 
    s.notify(e) 
} 

Một vài triển khai Sink giả định.

trait SystemEventSink extends Sink[SystemEvent] 

val ses = new SystemEventSink { 
    override def notify(o: SystemEvent): Unit = ??? 
} 

trait GenericEventSink extends Sink[Event] 

val ges = new GenericEventSink { 
    override def notify(o: Event): Unit = ??? 
} 

Các cuộc gọi phương pháp sau đây được chấp nhận bởi trình biên dịch:

appEventFired(new ApplicationEvent {}, ses) 

errorEventFired(new ErrorEvent {}, ges) 

appEventFired(new ApplicationEvent {}, ges) 

Nhìn vào hàng loạt các cuộc gọi bạn nhận thấy rằng người ta có thể gọi một phương thức mong đợi một Sink[ApplicationEvent] với một Sink[SystemEvent] và ngay cả với một Sink[Event]. Ngoài ra, bạn có thể gọi phương thức mong đợi là Sink[ErrorEvent] với Sink[Event].

Bằng cách thay thế bất biến bằng ràng buộc contravariance, một Sink[SystemEvent] trở thành một loại phụ của Sink[ApplicationEvent]. Do đó, contravariance cũng có thể được coi là một mối quan hệ 'mở rộng', vì các loại được 'mở rộng' từ cụ thể hơn đến chung chung hơn.

Kết luận

Ví dụ này đã được mô tả trong một loạt các bài viết về sai tìm thấy trên my blog

Cuối cùng, tôi nghĩ rằng nó giúp cũng hiểu lý thuyết đằng sau nó ...

+0

câu trả lời hay nhất tại đây –

Các vấn đề liên quan