2015-11-12 15 views
7

Si è imbattuto in questo strano comportamento quando si è modificato il limite superiore dell'implementazione, ma si è dimenticato di modificarlo nell'interfaccia. Penso che l'ultima affermazione non dovrebbe essere compilata, ma lo fa e restituisce risultati inaspettati.Strano comportamento di inferenza di tipo in funzione con limite superiore

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 =>() } 

Modifica

Come @ som-snytt menzionato con -Xprint:typer opzione possiamo vedere ciò che realmente compilatore inferisce:

s.doWork[Base with SuperBaseImpl] 

Questo spiega il motivo per cui stiamo ottenendo "interfaccia di base". Ma ancora non capisco come e perché digitare il lavoro di inferenza in questo caso.

risposta

1

Nota che ciò che il codice sta dicendo è:

Metodo ServeImp.doWork deve accettare un parametro che è "una funzione che deve accettare qualche classe T che è un sublass di base e Superbase"

SuperBaseImpl non è una sottoclasse di Base, ma non è un errore perché potrebbe esistere una classe X che "estende SuperBaseImpl con Base" che soddisferà tale requisito.

Quando si verifica l'inferenza del tipo, T è risolto in "foo.Base con foo.SuperBaseImpl" che soddisfa tutti i requisiti precedenti. runtimeClass è un'interfaccia di base perché non esiste un modo per descrivere quel tipo nella JVM in fase di esecuzione, ma se si manifest.toString si vedrà il tipo corretto.

Non c'è modo vero e proprio per dimostrare che con il tuo esempio, ma si consideri il seguente:

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 

Qui ho fatto DoWork tornare un'altra funzione che accetta T e si può vedere quello che ha deliberato di e che puoi effettivamente chiamarlo e funzionerà correttamente se passi qualcosa che corrisponde ai vincoli su tutti i tipi.

Aggiunto:

Si noti inoltre che la gerarchia di classe non è necessario dimostrare che il comportamento a tutti, si può essere del tutto estranei.

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

Sembra strano ma sembra sano. Si noti che è possibile anche chiamare

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

Penso solo che il parametro di tipo T è in qualche modo "disabitata". Il metodo non può sapere nulla di T eccetto il limite superiore Base, pertanto si ottiene un manifest per Base. Ma di nuovo con questo non puoi fare molto, perché non è possibile costruire un valore di tipo T ... Quindi tutto rimane sano.

Prova cambiando la firma

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

Quindi non è possibile utilizzare in questo modo:

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

Grazie, ma penso che sia più semplice definire semplicemente 'T' esplicitamente:' s.doWork [SuperBaseImpl] {x =>()} '. –

3

Con -Xprint:typer, vedrete cosa il compilatore deduce per T:

s.doWork[Base with SuperBaseImpl] 

Qual è il limite che prova ad esprimere? Le funzioni sono co-variant nel parametro, quindi stai esprimendo che body deve accettare un certo argomento di un tipo sufficientemente stretto. Normalmente, è necessario che una funzione si occupi di un tipo ampio.

Forse intendevi un limite inferiore.

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

Grazie per il trucco con '-Xprint: typer', rende tutto un po 'più chiaro per me. Ma ancora non capisco come e perché funziona in questo modo. E 'un errore? A proposito dei limiti, il limite superiore è stato usato per chiamare alcuni metodi definiti in Base/SuperBase prima/dopo aver chiamato la funzione argomento. E poi è stato cambiato in Impl solo per errore. –