2010-08-06 5 views
14

Je sais que l'effacement de type les rend égaux, type sage, lors de l'exécution, de sorte que:Comment différencier def foo [A] (xs: A *) et def foo [A, B] (xs: (A, B) *)?

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2)) } 
} 

donne l'erreur du compilateur suivant:

<console>:7: error: double definition: 
method foo:[A,B](xs: (A, B)*)Unit and 
method foo:[A](xs: A*)Unit at line 6 
have same type after erasure: (xs: Seq)Unit 
     def foo[A,B](xs: (A, B)*) { xs.foreach(x => println(x._1 + " - " + x._2) 
) } 
      ^

Mais est-il un moyen simple d'être capable d'écrire:

bar.foo(1, 2, 3) 
bar.foo(1 -> 2, 3 -> 4) 

et ayant ces différentes appellent versions surchargées de foo, sans avoir à les nommer explicitement:

bar.fooInts(1, 2, 3) 
bar.fooPairs(1 -> 2, 3 -> 4) 
+0

La méthode la plus simple consiste à utiliser un ClassManifest lié au contexte pour chacun des paramètres de type: 'def foo [A : ClassManifest] (xs: A *) ... '. J'ai ajouté ceci comme une réponse avec plus de commentaires ci-dessous. –

+0

En cas de surcharge, considérez: http://stackoverflow.com/questions/2510108/why-avoid-method-overloading –

+0

Voir aussi: http://stackoverflow.com/questions/3307427/scala-double-definition-2-methods- avoir le même type d'effacement – retronym

Répondre

15

Vous pouvez, d'une manière assez ronde. Foo est une classe de type, et le compilateur transmet implicitement une instance de la classe de type, compatible avec le paramètre de type (inféré) A.

trait Foo[X] { 
    def apply(xs: Seq[X]): Unit 
} 

object Foo { 
implicit def FooAny[A]: Foo[A] = new Foo[A] { 
    def apply(xs: Seq[A]) = println("apply(xs: Seq[A])") 
    } 
    implicit def FooTuple2[A, B]: Foo[(A, B)] = new Foo[(A, B)] { 
    def apply(xs: Seq[(A, B)]) = println("apply(xs: Seq[(A, B)])") 
    } 

    def apply[A](xs: A*)(implicit f: Foo[A]) = f(xs) 
} 


Foo(1, 2, 3)  // apply(xs: Seq[A]) 
Foo(1 -> 2, 2 -> 3) // apply(xs: Seq[(A, B)]) 

Dans le deuxième appel, les deux FooAny et FooTuple2 pourrait être passé, mais le compilateur ramasse FooTuple2, sur la base des règles de surcharge de méthode statique. FooTuple2 est considéré plus spécifique que FooAny. Si deux candidats sont considérés comme aussi spécifiques l'un de l'autre, une erreur d'ambiguïté est soulevée. Vous pouvez également préférer l'un à l'autre en en plaçant un dans une superclasse, comme cela est fait au scala.LowPriorityImplicits.

MISE À JOUR

Riffing de l'idée DummyImplicit, et le fil de suivi sur scala-utilisateur:

trait __[+_] 
object __ { 
implicit object __ extends __[Any] 
} 

object overload { 
def foo(a: Seq[Boolean]) = 0 

def foo[_: __](a: Seq[Int]) = 1 

def foo[_: __ : __](a: Seq[String]) = 2 
} 

import overload._ 
foo(Seq(true)) 
foo(Seq(1)) 
foo(Seq("s")) 

Ce déclare un trait de type paramétrées __, covariant dans son paramètre de type sans nom _. Son objet compagnon __ contient une instance implicite de __[Any], dont nous aurons besoin plus tard. Les deuxième et troisième surcharges de foo comprennent des paramètres de type fictif, encore une fois sans nom. Ceci sera déduit comme Any. Ce paramètre de type a une ou plusieurs bornes de contexte, qui sont Dessucré en paramètres implicites supplémentaires, par exemple:

def foo[A](a: Seq[Int])(implicit ev$1: __[A]) = 1 

Les multiples listes de paramètres sont concaténés en une seule liste de paramètres dans le bytecode, de sorte que le problème de la double définition est contournée .

Considérez ceci comme une opportunité d'apprendre l'effacement, les limites de contexte et la recherche implicite, plutôt que comme un modèle à appliquer en code réel!

+0

Cela a l'air génial, mais vous retournez l'unité là-haut ... Qu'est-ce que nous sommes limités à faire dans cette déclaration? Devons-nous être en mesure d'inverser les types seulement de la déclaration de ce qui s'applique? – dividebyzero

3
class Bar { 
    def foo[A](xs: A*) { xs.foreach{ 
     case (a,b) => println(a + " - " + b) 
     case a => println(a)} 
    } 
} 

Cela permettra

bar.foo(1,2) 
bar.foo(1->3,2->4) 

mais aussi permettre

bar.foo(1->2,5) 
4

Si vous ne me dérange pas de perdre la possibilité d'appeler foo avec zéro arguments (un vide Seq, si vous aimez), alors cette astuce peut aider:

def foo[A](x: A, xs: A*) { x::xs.foreach(println) } 
def foo[A, B](x: (A, B), xs: (A, B)*) { (x::xs.toList).foreach(x => println(x._1 + " - " + x._2)) } 

Je ne peux pas vérifier si cela fonctionne maintenant (pas même s'il compile), mais je pense que l'idée principale est assez facile à comprendre: le type du premier paramètre ne sera pas effacé, donc le compilateur peut faire la différence en fonction de cette.

Malheureusement, ce n'est pas très pratique si vous avez déjà un Seq et que vous voulez le passer à foo.

+0

C'est en fait un bon hack. Btw, vous devez faire (x :: xs.toList) .foreach (...). –

+0

Merci pour la correction, je l'ai réparé. –

2

Il y a une autre façon hacky pour obtenir ce travail: Coller un argument implicite sans rapport sur l'une des méthodes suivantes:

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*)(implicit s:String) { xs.foreach(x => println(x._1 + " - " + x._2)) } 
} 

implicit val s = "" 

new Bar().foo(1,2,3,4) 
//--> 1 
//--> 2 
//--> 3 
//--> 4 
new Bar().foo((1,2),(3,4)) 
//--> 1 - 2 
//--> 3 - 4 
8

Dans le cas où nous avons seulement deux surcharges, nous pouvons simplifier Landei's answer et éviter la besoin de définir notre propre implicite, en utilisant scala.Predef.DummyImplicit qui est automatiquement importé dans chaque portée pour vous.

class Bar { 
    def foo[A](xs: A*) { xs.foreach(println) } 
    def foo[A, B](xs: (A, B)*)(implicit s:DummyImplicit){ 
    xs.foreach(x => println(x._1 + " - " + x._2)) 
    } 
} 
+1

En fait, ceci n'est pas limité à seulement 2 surcharges. Il fonctionne avec un nombre arbitraire de surcharges, à condition que chaque surcharge ait un nombre différent de paramètres DummyImplicit. –

+0

Je pense que vous pourriez être intéressé - J'ai posté à ce sujet sur la liste des utilisateurs de scala: http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic -type-erasure-td2327664.html –

+0

Correction d'une seule ligne (en fait, juste deux mots), c'est vraiment doux. Vous vous demandez ce que Scala devrait avoir de "convenable" pour éviter la nécessité d'une solution de contournement. – bjfletcher

3

Cela semble moins compliqué que retronym's method, et est une version un peu moins bavard (quoique moins générale) de Ken Bloom's DummyImplicit solution:

class Bar { 
    def foo[A : ClassManifest](xs: A*) = { xs.foreach(println) } 

    def foo[A : ClassManifest, B : ClassManifest](xs: (A, B)*) = { 
     xs.foreach(x => println(x._1 + " - " + x._2)) 
    } 

    def foo[A : ClassManifest, 
      B : ClassManifest, 
      C : ClassManifest](xs: (A, B, C)*) = { 
     xs.foreach(x => println(x._1 + ", " + x._2 + ", " + x._3)) 
    } 
} 

Cette technique peut également être utilisée si vous avez deux surcharges avec le même nombre de paramètres de type:

class Bar { 
    def foo[A <: Int](xs: A*) = { 
     println("Ints:"); 
     xs.foreach(println) 
    } 

    def foo[A <: String : ClassManifest](xs: A*) = { 
     println("Strings:"); 
     xs.foreach(println) 
    } 
} 
+0

Je ne pense pas que ce soit plus général, car cela dépend du fait que les différentes surcharges ont des nombres différents de paramètres génériques. Cette technique ne fonctionnerait pas pour une ambiguïté entre 'foo (xs: Int *)' et 'foo (xs: String *)'. –

+0

C'est plus général dans le sens où il n'est pas limité à 2 surcharges. Il couvre également tout ensemble de 2 surcharges, à condition que vous omettiez le contexte lié à l'une des surcharges. –

+0

Je suppose que cela fonctionne dans toutes les situations où vous pouvez passer des nombres différents de 'ClassManifests'. D'un autre côté, vous pouvez le faire avec des nombres différents de 'DummyImplicits'. –

Questions connexes