2017-04-05 2 views
6

Pourquoi ce qui suit ne compilera pas à Scala:Pourquoi surcharger les méthodes polymorphes avec différentes limites supérieures compilez pas Scala

class A 
class B 

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

avec le message d'erreur

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

À ma connaissance, après l'effacement de type, def f[Q <: A](q: Q): Q doit être remplacé par sa limite supérieure: def f(q: A): Any et le second surchargé f en conséquence. Donc, ils devraient être distinguables après effacement de type. Alors, pourquoi Scala se plaint quand même?

+2

J'ai trouvé cet ancien article sur ce qui semble être les mêmes problèmes. http://www.scala-lang.org/old/node/4625.html – mdm

+0

donc, après avoir lu ce thread, cela signifie que c'est "juste" un bogue dans le compilateur Scala qui ne sera probablement jamais corrigé; mais travaille avec optimisme dans Dotty. En outre, si vous rediffusez ce commentaire comme réponse, je l'accepterai comme réponse correcte. –

Répondre

3

Ré-ajouter un commentaire comme réponse pour améliorer la visibilité.

Je trouve ce vieux post sur ce qui semble être la même question: http://www.scala-lang.org/old/node/4625.html

Il semble être un problème connu avec le compilateur Scala, avoir à faire plus avec le fait qu'il serait difficile de supporte cette fonctionnalité sans casser les autres fonctionnalités (Scala seulement) et les garanties du compilateur. Le poste montre également quelques solutions de contournement.

Il serait très intéressant si n'importe quel gourou du compilateur ici sur SO était en mesure de faire la lumière sur Dotty - ou devrais-je dire Skala? ;) - prévoit de le fixer.

+3

Skala ne le supportera que si vous nommez le type avec plus de 20 caractères (de préférence en Deutsch), donc le compilateur pourrait facilement faire la distinction entre le cas classique et avancé;) – dk14

2

C'est non seulement Scala l'appui, Java prennent pas en charge cela aussi:

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

Il est égal à Java:

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

Comme tout ce que nous savons, le type effacement va supprimer le type dans le runtime, par exemple, il y a un type C est le sous-type à la fois de A et B, dans le runtime, il va confondre f devrait apply.

Il y a un extrait de code peut-être il est utile de comprendre:

Comme Scalatrait:

trait A 
trait B 

class C extends A with B 

donc C est à la fois sous-classe de A et B. Si, lors de la transmission à la méthode f, il provoque des confusions dans runtime.

donc dans Java est le même, nous pouvons utiliser interface pour indiquer sous-classe. cela provoquera également des confusions dans runtime. exemple:

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

Mais les types émis ne sont pas de type 'Object'. Ils sont de type 'q: A' et' q: B', puisqu'ils ont une limite supérieure. –

+0

@YuvalItzchakov, j'ai ajouté un 'trait mixte' (interface pour ** Java **) exemple pour ce point, peut-être c'est utile – chengpohi

+0

ce n'est pas vrai comme expliqué par http://stackoverflow.com/a/43229647/5483583 –

5

Juste pour compléter réponse @chengpohi, vous pouvez réellement mettre en œuvre dispatching statique (surcharge est un cas particulier) de type classes:

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] 

La raison pour laquelle cela ne fonctionne pas naturellement est seulement que Scala doit imiter la surcharge de Java (avec son effacement) pour garder le code compatible avec le code Java externe et les caractéristiques internes et les garanties de Scala. Surcharge dans votre cas (mais pas toujours) est essentiellement un appel statique, il peut donc être traitée dans la compilation, mais de invokestatic machine virtuelle Java ne dispatching in runtime malheureusement:

Avant d'effectuer la méthode invokation, la classe et la méthode identifiés par sont résolus. Voir le chapitre 9 pour une description de la façon dont les méthodes sont résolues.

invokestatic examine le descripteur donné en, et détermine le nombre d'arguments pris par la méthode (cela peut être zéro). Il retire ces arguments de la pile d'opérandes. Ensuite, il recherche dans la liste des méthodes statiques définies par la classe, en localisant la méthode nom_méthode avec un descripteur de descripteur.

Alors, peu importe qu'il sait Q <: A restriction - il ne connaît pas le type formel de Q dans le temps d'exécution, de sorte que certains cas comme celui-là pointé par @chengpohi semblent pas possible de détecter ou résoudre (en fait ils pourraient le faire sur la base de l'information de la linéarisation - le seul inconvénient est l'implication du type d'exécution dans la répartition).


Haskell, par exemple, détermine la bonne méthode en compilation (pour autant que je sache), tapez donc les classes sont en mesure de compenser le manque de dispatching vraiment statique en décidant la bonne méthode pour appeler à la compilation.

P.S. Notez que dans Haskell, la surcharge est utilisée pour la distribution dynamique (correspondance de modèle) et les classes pour une répartition statique, c'est donc inversement par rapport à Java.

2

Je tiens à souligner que ce qui précède est possible en Java. Il se comporte comme prévu pour a et b. Je peux voir le problème avec les traits dans Scala à cause de plusieurs mixins, mais l'hérédité multiple des classes n'est pas possible.

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); 
    } 
}