In fin dei conti si tratta di type erasure (utilizzato sia da Java che da Scala).
Immaginate questo codice:
object Foo {
def foo(p: String) = 1
def foo(p: Int) = 2
def foo(p: Any) = 3
}
object Main extends App {
Foo.foo("1")
}
Tutto è bene qui. Ma cosa succede se cambiamo i parametri da singoli valori a una sequenza?
object Foo {
def foo(ps: String*) = 1
def foo(ps: Int*) = 2
def foo(ps: Any*) = 3
}
object Main extends App {
Foo.foo("1")
}
Ora abbiamo un errore:
Main.scala:4: error: double definition:
def foo(ps: Int*): Int at line 3 and
def foo(ps: Any*): Int at line 4
have same type after erasure: (ps: Seq)Int
def foo(ps: Any*) = 3
^
Main.scala:3: error: double definition:
def foo(ps: String*): Int at line 2 and
def foo(ps: Int*): Int at line 3
have same type after erasure: (ps: Seq)Int
def foo(ps: Int*) = 2
^
two errors found
E vedere il messaggio "hanno lo stesso tipo dopo la cancellazione" - questo è il nostro indizio.
Quindi, perché la sequenza ha avuto esito negativo?
Poiché la JVM non supporta i generici, ciò significa che le vostre raccolte di tipi fortemente (come la sequenza di interi o stringhe), in realtà non lo sono. Sono compilati in contenitori di Object perché è quello che si aspetta la JVM. I tipi sono "cancellati".
Così, dopo la compilazione di tutti questi simile a questa (vedremo esattamente quello che sono in un momento):
object Foo {
def foo(ps: Object*) = 1
def foo(ps: Object*) = 2
def foo(ps: Object*) = 3
}
Ovviamente non è questo ciò che si intendeva.
Quindi, come si comporta Java con questo?
Crea Bridge Methods dietro le quinte. Magia!
Scala non fa quella magia (sebbene sia stata discussed) - piuttosto usa impliciti fittizi.
Cambiamo la nostra definizione
object Foo {
def foo(ps: String*) = 1
def foo(ps: Int*)(implicit i: DummyImplicit) = 2
def foo(ps: Any*)(implicit i1: DummyImplicit, i2: DummyImplicit) = 3
}
E ora compila! Ma perché?
Guardiamo il codice che ha generato scalac (scalac foo.scala -print)
object Foo extends Object {
def foo(ps: Seq): Int = 1;
def foo(ps: Seq, i: Predef$DummyImplicit): Int = 2;
def foo(ps: Seq, i1: Predef$DummyImplicit, i2: Predef$DummyImplicit): Int = 3;
def <init>(): Foo.type = {
Foo.super.<init>();
()
}
};
OK - quindi abbiamo tre metodi Foo distinte che differiscono solo per i loro parametri impliciti.
E ora chiamiamoli così:
object Main extends App {
Foo.foo("1")
Foo.foo(1)
Foo.foo(1.0)
}
E questo cosa assomigliano (sto togliendo un sacco di altro codice qui ...)
Foo.foo(scala.this.Predef.wrapRefArray(Array[String]{"1"}.$asInstanceOf[Array[Object]]()));
Foo.foo(scala.this.Predef.wrapIntArray(Array[Int]{1}), scala.Predef$DummyImplicit.dummyImplicit());
Foo.foo(scala.this.Predef.genericWrapArray(Array[Object]{scala.Double.box(1.0)}), scala.Predef$DummyImplicit.dummyImplicit(), scala.Predef$DummyImplicit.dummyImplicit());
Così ogni chiamata è stato dato il parametro implicito necessario per disambiguare correttamente la chiamata.
Quindi perché esiste DummyImplicit? Per assicurarti che ci sia un tipo per il quale ci sarà sempre un valore implicito (altrimenti dovresti assicurarti che fosse disponibile).
La documentazione indica "Un tipo per il quale esiste sempre un valore implicito". - quindi esiste sempre il valore implicito da utilizzare in casi come questo.
Sei andato al codice sorgente di 'scala.Array $' metodo 'fallbackCanBuildFrom' come suggerisce il commento? Forse ti dà la più pallida idea di cosa sia. – Jesper