2016-04-13 4 views
2
case class Level[B](b: B){ 
    def printCovariant[A<:B](a: A): Unit = println(a) 
    def printInvariant(b: B): Unit = println(b) 
    def printContravariant[C>:B](c: C): Unit = println(c) 
} 

class First 
class Second extends First 
class Third extends Second 

//First >: Second >: Third 

object Test extends App { 

    val second = Level(new Second) //set B as Second 

    //second.printCovariant(new First) //error and reasonable 
    second.printCovariant(new Second) 
    second.printCovariant(new Third) 

    //second.printInvariant(new First) //error and reasonable 
    second.printInvariant(new Second) 
    second.printInvariant(new Third) //why no error? 

    second.printContravariant(new First) 
    second.printContravariant(new Second) 
    second.printContravariant(new Third) //why no error? 
} 

Es scheint scala die lowbound Typprüfung hat Bugs ... für invariante Fall und kontravarianten Fall.Scala type lowerbound bug?

Ich frage mich, ob oben Code sind Fehler oder nicht.

Antwort

4

Denken Sie immer daran, dass, wenn Third erweitert Second ist, wenn immer ein Second gewünscht wird, ein Third bereitgestellt werden kann. Dies wird als Subtyp polymorhpism bezeichnet.

In diesem Sinne ist es natürlich, dass second.printInvariant(new Third) kompiliert. Sie haben eine Third zur Verfügung gestellt, die eine Unterart von Second ist, so dass es auscheckt. Es ist, als würde man einem Apfel eine Methode geben, die eine Frucht nimmt. Diese

bedeutet, dass Ihre Methode

def printCovariant[A<:B](a: A): Unit = println(a) 

kann geschrieben werden:

def printCovariant(a: B): Unit = println(a) 

, ohne Informationen zu verlieren. Aufgrund des Subtyp-Polymorphismus akzeptiert der zweite B und alle seine Unterklassen, was derselbe wie der erste ist.

Das gleiche gilt für Ihren zweiten Fehlerfall - es ist ein weiterer Fall von Subtyp Polymorphismus. Sie können die neue Third übergeben, weil Third tatsächlich eine Sekunde ist (beachten Sie, dass ich die "is-a" Beziehung zwischen Unterklasse und Oberklasse aus der objektorientierten Notation verwende).

Falls Sie sich fragen, warum haben wir auch obere Schranken müssen, beachten Sie dieses Beispiel aus (ist genug Polymorphismus nicht Subtyp?):

def foo1[A <: AnyRef](xs: A) = xs 
def foo2(xs: AnyRef) = xs 
val res1 = foo1("something") // res1 is a String 
val res2 = foo2("something") // res2 is an Anyref 

Jetzt machen wir den Unterschied bemerken. Obwohl der Subtyp-Polymorphismus es uns erlaubt, in beiden Fällen einen String zu übergeben, kann nur die Methode foo1 den Typ des Arguments (in unserem Fall ein String) referenzieren. Methode foo2 nimmt glücklich eine Zeichenfolge, aber wird nicht wirklich wissen, dass es eine Zeichenfolge ist. Obere Grenzen können also nützlich sein, wenn Sie den Typ beibehalten möchten (in Ihrem Fall drucken Sie einfach den Wert aus, so dass Sie sich nicht wirklich um den Typ kümmern - alle Typen haben eine toString-Methode).

EDIT:
(zusätzliche Details, können Sie bereits wissen, aber ich werde es auf Vollständigkeit stellen)

Es gibt mehrere Verwendungszwecke obere Schranken dann, was ich hier beschrieben, aber bei der Parametrierung Eine Methode ist das häufigste Szenario. Wenn Sie eine Klasse parametrisieren, können Sie obere Grenzen zur Beschreibung der Kovarianz und unteren Grenzen zur Beschreibung der Kontravarianz verwenden. Zum Beispiel

class SomeClass[U] { 

    def someMethod(foo: Foo[_ <: U]) = ??? 

} 

sagt, dass Parameter foo der Methode someMethod in seiner Art kovariant ist. Wie ist das? Nun, normalerweise (dh ohne Varianz zu verändern) würde es uns der Subtyp-Polymorphismus nicht erlauben, einen Foo Parameter zu übergeben, der mit einem Subtyp seines Typparameters parametrisiert ist. Wenn T <: U, bedeutet dies nicht, dass Foo[T] <: Foo[U]. Wir sagen, dass Foo in seinem Typ invariant ist. Aber wir haben nur die Methode optimiert, um Foo mit U oder einer seiner Subtypen akzeptiert zu akzeptieren.Nun, das ist effektiv Kovarianz. Solange also someMethod betroffen ist - wenn ein Typ T ein Untertyp von U ist, dann ist Foo[T] ein Untertyp von Foo[U]. Großartig, wir haben Kovarianz erreicht. Aber beachte, dass ich sagte "solange someMethod betroffen ist". Foo ist in dieser Methode in seinem Typ kovariant, aber in anderen kann es invariant oder kontravariant sein.

Diese Art der Varianz Erklärung Nutzung Ort Varianz genannt, weil wir die Varianz einer Art an der Stelle seiner Verwendung (hier es verwendet als Methode Parametertyp someMethod) deklarieren. Dies ist die einzige Art von Varianzdeklaration in Java. Bei der Verwendung von Site-Varianz haben Sie auf das Get-Put-Prinzip achten (google it). Im Grunde besagt dieses Prinzip, dass wir nur Zeug aus kovarianten Klassen bekommen können (wir können nicht setzen) und umgekehrt für kontravariante Klassen (wir können setzen, aber nicht bekommen). In unserem Fall können wir es wie folgt zeigen:

class Foo[T] { def put(t: T): Unit = println("I put some T") } 

def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile 
def someMethod2(foo: Foo[_ >: String]) = foo.put("asd") 

Allgemeiner können wir nur covariant Typen als Rückgabetypen und kontra-Typen als Parametertypen verwenden.

Jetzt ist use-site-Deklaration schön, aber in Scala ist es viel häufiger, Deklaration-Site- Varianz zu nutzen (etwas, das Java nicht hat). Dies bedeutet, dass wir die Varianz des generischen Typs Foo zum Zeitpunkt der Definition von Foo beschreiben würden. Wir würden einfach class Foo[+T] sagen. Jetzt müssen wir beim Schreiben von Methoden, die mit Foo arbeiten, keine Grenzen verwenden; wir haben Foo proklamiert, um in seinem Typ, in jedem Anwendungsfall und jedem Szenario dauerhaft kovariant zu sein.

Für weitere Details über die Varianz in Scala zögern Sie nicht meine blog post zu diesem Thema.