2015-11-12 6 views
7

Ran in dieses seltsame Verhalten, wenn obere Grenze in der Implementierung geändert, aber vergessen, es in der Schnittstelle zu ändern. Ich denke, letzte Anweisung sollte nicht kompilieren, aber es tut und gibt unerwartetes Ergebnis zurück.Seltsames Verhalten der Typrückschluss in Funktion mit Obergrenze

trait SuperBase 
trait Base extends SuperBase 

class SuperBaseImpl extends SuperBase 

trait Service { 
    def doWork[T <: Base : Manifest](body: T => Unit): String 
    def print[T <: Base : Manifest]: String 
} 

object ServiceImpl extends Service { 
    override def doWork[T <: SuperBase : Manifest](body: T => Unit): String = 
    print[T] 
    def print[T <: SuperBase : Manifest]: String = 
    manifest[T].runtimeClass.toString 
} 

val s: Service = ServiceImpl 

// does not compile as expected 
// s.print[SuperBaseImpl] 

// returns "interface Base" 
s.doWork { x: SuperBaseImpl =>() } 

bearbeiten

Wie @ som-snytt mit -Xprint:typer Option erwähnt können wir sehen, welche Compiler folgert tatsächlich:

s.doWork[Base with SuperBaseImpl] 

Dies erklärt, warum wir "Schnittstelle Base" bekommen. Aber ich verstehe immer noch nicht ganz, wie und warum Inferenz in diesem Fall funktioniert.

Antwort

1

Beachten Sie, dass, was Ihr Code sagt, ist:

Methode ServeImp.doWork einen Parameter annehmen, die „eine Funktion, die eine Klasse T annehmen muss, dass eine sublass of Base und Superbase ist“ ist

SuperBaseImpl ist keine Unterklasse von Base, aber es ist kein Fehler, weil es eine Klasse X geben könnte, die "SuperBaseImpl mit Base" erweitert, die diese Anforderung erfüllen würde.

Wenn Typ-Inferenz auftritt, wird T zu "foo.Base mit foo.SuperBaseImpl" aufgelöst, die alle obigen Anforderungen erfüllt. runtimeClass ist die Schnittstelle Base, da es keine Möglichkeit gibt, diesen Typ in der JVM zur Laufzeit zu beschreiben, aber wenn Sie manifest.toString - Sie sehen den richtigen Typ.

Es gibt keine wirkliche Möglichkeit, dass mit Ihrem Beispiel zu demonstrieren, sondern betrachten Sie das folgende:

trait SuperBase 
trait Base extends SuperBase 

class SuperBaseImpl(val a: String) extends SuperBase 

trait Service { 
    def doWork[T <: Base : Manifest](body: T => String): (T) => String 
} 

object ServiceImpl extends Service { 
    override def doWork[T <: SuperBase : Manifest](body: T => String): (T) => String = 
    x => "Manifest is '%s', body returned '%s'".format(manifest[T].toString(), body(x)) 
} 

val s: Service = ServiceImpl 

val f = s.doWork { x: SuperBaseImpl => x.a } 
// f: Base with SuperBaseImpl => String = <function1> 

f(new SuperBaseImpl("foo") with Base) 
// res0: String = Manifest is 'Base with SuperBaseImpl', body returned 'foo' 

f(new SuperBaseImpl("foo")) 
// compile error 

Hier habe ich gemacht doWork eine andere Funktion zurück, die T akzeptiert und Sie können sehen, was es zu lösen, und dass Sie das tatsächlich aufrufen können, und es funktioniert ordnungsgemäß, wenn Sie etwas übergeben, das den Einschränkungen für alle Typen entspricht.

Hinzugefügt:

Beachten Sie auch, dass Ihre Klassenhierarchie nicht notwendig ist, dass das Verhalten überhaupt zu zeigen, können sie völlig unabhängig sein.

trait A 
trait B 

def m[T <: A : Manifest](body: T => Unit) = manifest[T].toString() 

m((x: B) => Unit) 
//res0: String = A with B 
1

Es sieht komisch aus, aber fühlt sich gut an. Beachten Sie, dass Sie auch

s.doWork { x: Any =>() } 

nennen kann Ich denke nur, dass der Typ-Parameter T irgendwie „unbewohnt“ ist. Die Methode kann nichts über T außer seiner oberen Grenze Base wissen, daher erhalten Sie ein Manifest für Base. Aber auch damit kann man nicht viel anfangen, denn das kann keinen Wert vom Typ T aufbauen ... So bleibt alles solide.

Versuchen Sie, die Unterschrift zu

def doWork[T <: Base : Manifest](x: T)(body: T => Unit): String 

Ändern Dann können Sie nicht es auf diese Weise verwenden:

s.doWork(123: Int) { x: Any =>() } // no 
s.doWork(123: Any) { x: Any =>() } // no 
+0

Danke, aber ich denke, es ist einfacher, 'T' explizit zu definieren:' s.doWork [SuperBaseImpl] {x =>()} '. –

3

Mit -Xprint:typer, Sie werden sehen, was der Compiler für T folgert:

s.doWork[Base with SuperBaseImpl] 

Was ist der gebundene Versuch auszudrücken? Funktionen sind im Parameter co-variant, so dass Sie ausdrücken, dass body eine bestimmte arg eines ausreichend engen Typs akzeptieren muss. Normalerweise müssen Sie eine Funktion mit einem breiten Typ behandeln.

Vielleicht wollten Sie eine untere Grenze.

scala> trait SuperBase 
defined trait SuperBase 

scala> trait Base extends SuperBase 
defined trait Base 

scala> class SuperBaseImpl extends SuperBase 
defined class SuperBaseImpl 

scala> trait Service { def f[A >: Base : Manifest](g: A => Unit): String } 
defined trait Service 

scala> object Impl extends Service { def f[A >: Base : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
defined object Impl 

scala> (Impl: Service).f { x: Base =>() } 
res0: String = interface Base 

scala> (Impl: Service).f { x: SuperBase =>() } 
res1: String = interface SuperBase 

scala> (Impl: Service).f { x: SuperBaseImpl =>() } 
<console>:17: error: inferred type arguments [SuperBaseImpl] do not conform to method f's type parameter bounds [A >: Base] 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
        ^
<console>:17: error: type mismatch; 
found : SuperBaseImpl => Unit 
required: A => Unit 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
              ^
<console>:17: error: No Manifest available for A. 
     (Impl: Service).f { x: SuperBaseImpl =>() } 
         ^

scala> object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
<console>:14: error: overriding method f in trait Service of type [A >: Base](g: A => Unit)(implicit evidence$1: Manifest[A])String; 
method f has incompatible type 
     object Impl extends Service { def f[A >: SuperBase : Manifest](g: A => Unit) = manifest[A].runtimeClass.toString } 
             ^
+0

Danke für den Trick mit '-Xprint: typer', es macht alles ein bisschen klarer für mich. Aber ich verstehe immer noch nicht, wie und warum es so funktioniert. Ist es ein Fehler? Über Grenzen wurde die obere Grenze verwendet, um einige in Base/SuperBase definierte Methoden vor/nach Aufruf der Argumentfunktion aufzurufen. Und dann wurde es in Impl nur aus Versehen geändert. –