2017-04-05 10 views
6

Dlaczego nie po kompilacji w Scala:Dlaczego przeciążenia polimorficzne metod z różnych górnych granic nie skompilować w Scala

class A 
class B 

object X { 
    def f[Q <: A](q: Q): Q = q 
    def f[Q <: B](q: Q): Q = q 
} 

z komunikatem o błędzie

<console>:16: error: method f is defined twice 
    conflicting symbols both originated in file '<console>' 
     def f[Q <: B](q: Q): Q = q 

mojego rozeznania, po typu skasowaniem, def f[Q <: A](q: Q): Q należy zastąpić górną granicą: def f(q: A): Any i odpowiednio drugą przeciążoną f. Więc powinny być odróżnialne po wymazaniu typu.

Dlaczego więc Scala narzeka?

+2

Znalazłem ten stary post na temat tego, co wydaje się być ten sam problem. http://www.scala-lang.org/old/node/4625.html – mdm

+0

, więc po przeczytaniu tego wątku oznacza to, że jest to "po prostu" błąd w kompilatorze Scali, który prawdopodobnie nigdy nie zostanie naprawiony; ale mam nadzieję, że działa w Dotty. Ponadto, jeśli odeślesz ten komentarz jako odpowiedź, zaakceptuję go jako poprawną odpowiedź. –

Odpowiedz

3

Ponowny komentarz jako odpowiedź w celu poprawy widoczności.

Znalazłem ten stary post na temat tego, co wydaje się być ten sam problem: http://www.scala-lang.org/old/node/4625.html

Wydaje się być znany problem z kompilatora Scala, mających do czynienia bardziej z faktu, że będzie trudno obsługuje tę funkcję bez łamania innych (Scala-only) funkcji i gwarancji kompilatora. Post pokazuje również kilka obejść.

Byłoby bardzo interesujące, gdyby jakikolwiek guru kompilator tutaj na SO był w stanie rzucić trochę światła na to, czy Dotty - czy powinienem powiedzieć Skala? ;) - planuje go naprawić.

+3

Skala będzie go obsługiwać tylko wtedy, gdy nazwiesz typ z więcej niż 20 znakami (najlepiej w języku Deutsch), więc kompilator może łatwo odróżnić klasyczne od zaawansowanego przypadku;) – dk14

2

To nie jest tylko Scala wsparcie tego Java nie obsługują tego też:

def f[Q <: A](q: Q): Q = q 
def f[Q <: B](q: Q): Q = q 

To równa Java:

public <Q extend A> Q f(q: Q) { return q;} 
public <Q extend B> Q f(q: Q) { return q;} 

Jak wszyscy wiemy, typu wymazanie spowoduje usunięcie typu w środowisku wykonawczym, na przykład istnieje typ C oba podtypy A i B, w środowisku wykonawczym, będą mylić, który f powinien być apply.

Jest to fragment kodu może być to pomocne w zrozumieniu tego:

Jak Scalatrait:

trait A 
trait B 

class C extends A with B 

tak C jest zarówno podklasa z A i B. jeśli po przejściu do metody f spowoduje to zamieszanie w runtime.

tak W Java to samo, możemy użyć interface stwierdzić podklasę. to również spowoduje zamieszanie w runtime. Przykład:

interface C { 
} 
interface D { 
} 
static class A implements D, C { 
} 
public <Q extends C> Q f(Q q) { 
    return q; 
} 
public <Q extends D> Q f(Q q) { 
    return q; 
} 
new TestTmp().f(new A()) // Ambiguous call in here. 
+0

Ale emitowane typy nie są typu "Obiekt". Są one odpowiednio typu "q: A" i "q: B", ponieważ mają górną granicę. –

+0

@YuvalItzchakov, Dodałem przykład "mieszana cecha" (interfejs dla ** Java **) dla tego punktu, może to pomocne – chengpohi

+0

to nie jest prawda, jak wyjaśniono przez http://stackoverflow.com/a/43229647/5483583 –

5

Wystarczy uzupełnić odpowiedź @chengpohi, rzeczywiście można wdrożyć dyspozytorskich statycznego (przeciążenie jest szczególnym przypadkiem) z klas typu:

trait A 
trait B 

implicit class RichA[Q <: A](q: Q){ def f = q } 

implicit class RichB[Q <: B](q: Q){ def f = q } 

scala> (new A{}).f 
res0: A = [email protected] 

scala> (new B{}).f 
res1: B = [email protected] 

Powodem dlaczego to nie działa w sposób naturalny jest tylko to, że Scala musi naśladować przeciążanie Javy (z jej usunięciem), aby zachować kompatybilność kodu z zewnętrznym kodem Java i wewnętrznymi funkcjami i gwarancjami Scala. Przeciążenie w Twoim przypadku (ale nie zawsze) jest w zasadzie statyczna wezwanie, więc może być przetwarzany w czasie kompilacji, ale JVM na invokestatic jest dysponowanie in runtime niestety:

Przed wykonaniem invokation metody, klasy i metody zidentyfikowane przez są rozwiązane. Zobacz Rozdział 9, aby dowiedzieć się, w jaki sposób zostały rozwiązane metody.

Invokestatyczny przegląd podanego deskryptora, a określa, ile argumentów metoda przyjmuje (może to być zero). To wyrzuca te argumenty ze stosu operandów. Następnie przeszukuje listę metod statycznych zdefiniowanych przez klasę, lokalizując metodę nazwa_metody z deskryptorem deskryptora.

Tak więc, niezależnie, że wie o Q <: A ograniczenie - nie wiedzieć o formalnym typu Q w czasie wykonywania, więc kilka takich przypadków, że jeden wskazanym przez @chengpohi wydają się niemożliwe do wykrycia lub postanowienie (właściwie mogli to zrobić na podstawie informacji z linearyzacji - jedyną wadą jest zaangażowanie typu uruchomieniowego w wysyłanie).


Haskell, na przykład, określa właściwą metodę w czasie kompilacji (o ile wiem), więc typu zajęcia są w stanie zrekompensować braku dyspozytorski naprawdę statycznej decydując właściwą metodę zadzwonić w czasie kompilacji.

P.S. Zauważ, że w Haskell przeciążanie jest używane do dynamicznego wysyłania (dopasowywania wzorców) i klas dla statycznych, więc jest to zasadniczo odwrotnie w porównaniu do Javy.

2

Chciałbym podkreślić, że powyższe jest możliwe w Javie. Zachowuje się zgodnie z oczekiwaniami dla a i b. Widzę problem z cechami Scali z powodu wielu mixinów, ale wielokrotne dziedziczenie klas nie jest możliwe.

public class A {} 
public class B {} 

public class Test { 
    public <Q extends A> Q f(Q q) { 
     System.out.println("with A"); 
     return q; 
    } 

    public <Q extends B> Q f(Q q) { 
     System.out.println("with B"); 
     return q; 
    } 

    public static void main(String[] args) { 
     final A a = new A() {}; 
     final B b = new B() {}; 

     final Test test = new Test(); 

     test.f(a); 
     test.f(b); 
    } 
} 
Powiązane problemy