2015-11-12 8 views
7

Wystąpiło to dziwne zachowanie po zmianie górnej granicy w implementacji, ale zapomniałem go zmienić w interfejsie. Myślę, że ostatnie oświadczenie nie powinno się kompilować, ale działa i zwraca nieoczekiwany rezultat.Dziwne zachowanie wnioskowania o typie w funkcji z górną granicą

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

Edit

Jako @ som-snytt wspomniano o -Xprint:typer opcji możemy zobaczyć, co kompilator faktycznie wywodzi:

s.doWork[Base with SuperBaseImpl] 

To wyjaśnia, dlaczego kupujemy "interfejs podstawowy". Ale wciąż nie do końca rozumiem, jak i dlaczego w tym przypadku działa inferencja typów.

Odpowiedz

1

Należy pamiętać, że to, co mówi Twój kod to:

Metoda ServeImp.doWork musi przyjmować parametr, który jest „funkcja, która musi przyjąć jakąś klasę T, który jest sublass of Base i super zasady”

SuperBaseImpl nie jest podklasą Base, ale nie jest to błąd, ponieważ może istnieć klasa X, która "rozszerza SuperBaseImpl z Base", która spełni to wymaganie.

W przypadku wnioskowania o typie, T jest rozpatrywane jako "foo.Base with foo.SuperBaseImpl", które spełnia wszystkie powyższe wymagania. runtimeClass jest interfejsem Base, ponieważ nie ma możliwości opisania tego typu w JVM w środowisku wykonawczym, ale jeśli wykonasz manifest.toString - zobaczysz poprawny typ.

Nie ma prawdziwego sposób wykazać, że ze swoim przykładzie, ale należy wziąć pod uwagę następujące elementy:

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 

Tutaj zrobiłem DoWork wrócić inną funkcję, która akceptuje T i można zobaczyć, co postanowił, i że faktycznie możesz to nazwać i będzie działać poprawnie, jeśli przekażesz coś, co pasuje do ograniczeń na wszystkich typach.

Dodano:

Należy również zauważyć, że hierarchia klasa nie jest konieczne wykazanie, że zachowanie w ogóle, mogą być całkowicie niezależne.

1

Wygląda dziwnie, ale brzmi dobrze. Pamiętaj, że możesz również zadzwonić

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

Po prostu myślę, że parametr typu T jest jakoś „niezamieszkane”. Metoda nie może nic wiedzieć o T oprócz górnej granicy Base, dlatego pojawia się manifest dla Base. Ale znowu z tym nie można wiele zrobić, ponieważ nie można skonstruować wartości typu T ... A więc wszystko pozostaje w porządku.

Spróbuj zmienić podpis

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

Wtedy nie można używać go w ten sposób:

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

Dzięki, ale myślę, że łatwiej jest po prostu zdefiniować 'T' jawnie:' s.doWork [SuperBaseImpl] {x =>()} '. –

3

Z -Xprint:typer, zobaczysz co kompilator wnioskuje o T:

s.doWork[Base with SuperBaseImpl] 

Co to jest granica próbująca wyrazić? Funkcje są współzmienne w parametrze, więc wyrażasz, że body musi zaakceptować pewien arg o wystarczająco wąskim typie. Zwykle wymaga się, aby funkcja zajmowała się szerokim typem.

Może zamierzałeś niższą granicę.

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

Dzięki za trik z '-Xprint: typer', wszystko jest dla mnie bardziej zrozumiałe. Ale nadal nie rozumiem, jak i dlaczego działa w ten sposób. Czy to błąd? O granice górna granica została użyta do wywołania niektórych metod zdefiniowanych w Base/SuperBase przed/po wywołaniu funkcji argumentu. A potem został zmieniony w Impl tylko przez pomyłkę. –

Powiązane problemy