scala method-overloading type-erasure

scala - ¿Cómo puedo diferenciar entre def foo[A](xs: A*) y def foo[A, B](xs:(A, B)*)?



method-overloading type-erasure (6)

Hay otra forma estrafalaria de hacer que esto funcione: Pegue un argumento implícito no relacionado en uno de los métodos:

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

Sé que el borrado de tipos hace que se vean iguales, tipo-sabio, en tiempo de ejecución, de modo que:

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

da el siguiente error de compilación:

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

¿Pero hay una manera simple de poder escribir?

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

y hacer que estos invoquen diferentes versiones sobrecargadas de foo, sin tener que nombrarlas explícitamente:

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


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

Esto permitira

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

Pero también permite

bar.foo(1->2,5)


Puedes, de una manera bastante redonda. Foo es una clase de tipo, y el compilador pasa implícitamente una instancia de la clase de tipo, compatible con el parámetro de tipo (inferido) 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)])

En la segunda llamada, se pudieron pasar tanto FooAny como FooTuple2 , pero el compilador elige FooTuple2 , basado en las reglas de sobrecarga de métodos estáticos. FooTuple2 se considera más específico que FooAny . Si se considera que dos candidatos son tan específicos entre sí, se genera un error de ambigüedad. También puede preferir uno sobre el otro colocando uno en una superclase, como se hace en scala.LowPriorityImplicits .

ACTUALIZAR

Riffing de la idea DummyImplicit y el hilo de seguimiento en scala-user:

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"))

Esto declara un rasgo parametrizado por tipo __ , covariante en su parámetro de tipo sin nombre _ . Su objeto compañero __ contiene una instancia implícita de __[Any] , que necesitaremos más adelante. La segunda y tercera sobrecarga de foo incluyen un tipo de parámetros ficticios, nuevamente sin nombre. Esto se deducirá como Any . Este parámetro de tipo tiene uno o más límites de contexto, que se desagregan en parámetros implícitos adicionales, por ejemplo:

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

Las listas de parámetros múltiples se concatenan en una sola lista de parámetros en el bytecode, por lo que se evita el problema de doble definición.

Por favor, considere esto como una oportunidad para aprender sobre el borrado, los límites de contexto y la búsqueda implícita, en lugar de como un patrón para aplicar en código real.


Si no le importa perder la posibilidad de llamar a foo con cero argumentos (una Seq vacía, si lo desea), este truco puede ayudar:

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

No puedo verificar si funciona ahora (ni siquiera si compila), pero creo que la idea principal es bastante fácil de entender: el tipo del primer parámetro no se borrará, por lo que el compilador puede marcar la diferencia en función de ese .

Desafortunadamente, tampoco es muy conveniente si ya tienes un Seq y quieres pasarlo a foo.


En el caso en que tengamos solo 2 sobrecargas, podemos simplificar la respuesta de Landei y evitar la necesidad de definir nuestra propia implícita, utilizando scala.Predef.DummyImplicit que se importa automáticamente en cada alcance para usted.

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


Esto parece menos complicado que el método de retronym , y es una versión ligeramente menos detallada (aunque menos general) de la solución DummyImplicit de Ken Bloom :

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

Esta técnica también se puede usar si tiene dos sobrecargas con el mismo número de parámetros de tipo:

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