2014-12-11 18 views
6

Ich habe gerade erfahren, Scala. Jetzt bin ich verwirrt über Kontravarianz und Kovarianz.Kontra vs Kovarianz in Scala

Von diesen page lernte ich etwas unter:

Kovarianzstrukturen

Vielleicht ist das auffälligste Merkmal von Subtyping ist die Fähigkeit, einen Wert eines größeren Typs mit einem Wert einer engeren Art in einem Ausdruck zu ersetzen, . Angenommen, ich habe einige Arten Real, Integer <: Real, und einigen nicht verwandten Typen Boolean. Ich kann eine Funktion is_positive :: Real -> Boolean definieren, die auf Real Werten funktioniert, aber ich kann diese Funktion auch auf Werte des Typs Integer (oder eines anderen Subtyps von Real) anwenden. Dieser Ersatz von breiteren (Vorfahren-) Typen durch schmalere (abgeleitete) Typen heißt covariance. Das Konzept von covariance ermöglicht es uns, generischen Code zu schreiben und ist von unschätzbarem Wert, wenn es um Vererbung in objektorientierten Programmiersprachen und Polymorphie in funktionalen Sprachen geht.

Aber ich sah auch etwas von woanders:

scala> class Animal
 defined class Animal 

scala> class Dog extends Animal
 defined class Dog 

scala> class Beagle extends Dog
 defined class Beagle 

scala> def foo(x: List[Dog]) = x
 foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
  

scala> val an: List[Animal] = foo(List(new Beagle))
 an: List[Animal] = List([email protected]) 

Parameter x von foo ist contravariant; es erwartet ein Argument vom Typ List[Dog], aber wir geben es eine List[Beagle], und das ist in Ordnung

[Was ich denke, ist das zweite Beispiel sollte auch Covariance beweisen. Weil ich aus dem ersten Beispiel gelernt habe, dass "diese Funktion auf Werte vom Typ Integer (oder einem anderen Subtyp von Real) angewendet wird". Entsprechend verwenden wir diese Funktion hier auf Werte vom Typ List[Beagle] (oder einen anderen Subtyp von List[Dog]). Aber zu meiner Überraschung erweist sich das zweite Beispiel Cotravariance]

Ich denke, zwei die gleiche Sache sprechen, aber man beweist Covariance und die anderen Contravariance. Ich sah auch this question from SO. Aber ich bin immer noch verwirrt. Habe ich etwas übersehen oder ist eines der Beispiele falsch?

Antwort

9

, dass Sie eine List[Beagle] an eine Funktion kann ein List[Dog] ist, nichts zu tun mit Kontra von Funktionen erwarten, es ist immer noch da Liste covariant ist und dass List[Beagle] ist ein List[Dog].

lässt Stattdessen sagen Sie eine Funktion hatte:

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int 

Diese Funktion zählt alle Beine in einer Liste von Hunden. Es nimmt eine Funktion an, die einen Hund akzeptiert und einen Int zurückgibt, der angibt, wie viele Beine dieser Hund hat.

lässt Weiterhin sagen, wir haben eine Funktion:

def countLegsOfAnyAnimal(a: Animal): Int 

, die die Beine eines Tieres zählen kann. Wir können unsere countLegsOfAnyAnimal Funktion an unsere countDogsLegs Funktion als das Funktionsargument übergeben, dieses ist, weil, wenn dieses Ding die Beine irgendeines Tieres zählen kann, es Beine der Hunde zählen kann, weil Hunde Tiere sind, dieses ist, weil Funktionen kontravariant sind.

Wenn Sie bei der Definition von Function1 (Funktionen eines Parameters) zu buchen, ist es

trait Function1[-A, +B] 

Das heißt, dass sie auf ihre Ein- und covariant an ihrem Ausgang kontra sind. So Function1[Animal,Int] <: Function1[Dog,Int] seit Dog <: Animal

+0

Gute Erklärung. Der Rückgabetyp von 'groomAnyAnimal' sollte' Dog' sein, um ihn zu verbinden, da Funktionen in ihrem Rückgabetyp und in ihrem Argumenttyp nur kovariant sind. –

+0

@stew also denkst du, dass die Aussagen des zweiten Beispiels irgendwie falsch sind? – CSnerd

+1

@CSnerd In Ihrem zweiten Beispiel möchten Sie irgendwie eine Aussage darüber machen, ob 'x' kovariant oder kontravariant ist. Wie bereits erwähnt, hat die Varianz jedoch nichts damit zu tun, ob Sie einen Subtyp übergeben können, für den ein Supertyp erforderlich ist (siehe [Loskow-Substitutionsprinzip] (http://en.wikipedia.org/wiki/Liskov_substitution_principle)). Das einzige, was in Ihrem zweiten Beispiel kovariant ist, ist 'List', daher ist' List [Beagle] 'ein Untertyp von' List [Dog] '. –

6

A Good kürzlich erschienener Artikel (August 2016) zu diesem Thema ist "Cheat Codes for Contravariance and Covariance" von Matt Handler.

Aus dem allgemeinen Konzept beginnt als answer in "Covariance and Contravariance of Hosts and Visitors" und Diagramm von Andre Tyukin und anoopelias ‚s vorgestellt.

http://blog.originate.com/images/variance.png

Und seine endet mit:

Hier ist, wie wenn Ihr bestimmen type ParametricType[T] kann/können nicht covariant/kontra sein:

  • Ein Typ kann covariant sein, wenn es ruft keine Methoden für den generischen Typ über auf.
    Wenn der Typ Methoden für generische Objekte aufrufen muss, die an ihn übergeben werden, kann er nicht kovariant sein.

Archetypal Beispiele:

Seq[+A], Option[+A], Future[+T] 
  • Ein Typ kontra sein kann, wenn es Anruf Methoden von der Art tut, dass es über generisch ist.
    Wenn der Typ Werte des generischen Typs zurückgeben muss, kann er nicht kontravariant sein.

Archetypal Beispiele:

`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]` 

In Bezug auf Kontra,

Funktionen sind das beste Beispiel für Kontra
(beachten Sie, dass sie nur kontra auf ihre Argumente, und sie sind eigentlich kovariant auf ihr Ergebnis).
Zum Beispiel:

class Dachshund(
    name: String, 
    likesFrisbees: Boolean, 
    val weinerness: Double 
) extends Dog(name, likesFrisbees) 

def soundCuteness(animal: Animal): Double = 
    -4.0/animal.sound.length 

def weinerosity(dachshund: Dachshund): Double = 
    dachshund.weinerness * 100.0 

def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean = 
    f(dog) >= 0.5 

Sollten wir in der Lage sein weinerosity als Argument an isDogCuteEnough passieren?Die Antwort ist nein, weil die Funktion isDogCuteEnough nur garantiert, dass sie höchstens Dog an die Funktion f übergeben kann.
Wenn die Funktion f erwartet etwas präziser als das, was isDogCuteEnough zur Verfügung stellen kann, ist es, ein Verfahren zu nennen versuchen könnte, die einige Dogs nicht haben (wie .weinerness auf einem Greyhound, was verrückt ist).

0

Variance wird verwendet Subtyping in Bezug auf Container anzuzeigen (zB: List). Wenn in den meisten Sprachen eine Funktion ein Objekt von Class Animal anfordert, wäre die Übergabe einer Klasse, die Animal (z. B. Dog) erbt, gültig. In Bezug auf Container müssen diese jedoch nicht gültig sein. Wenn Ihre Funktion Container[A] möchte, welche Werte können an sie übergeben werden? Wenn BA verlängert und Container[B] gültig ist, dann ist es Covariant (zB: List[+T]). Wenn, A erweitert B (der umgekehrte Fall) und Container[B] für Container[A] ist gültig ist, dann ist es Contravariant. Sonst ist es invariant (was der Standard ist). Sie könnten auf einen Artikel verweisen, wo ich versucht habe, Varianzen in Scala https://lakshmirajagopalan.github.io/variance-in-scala/ zu erklären.