I know that type erasure makes them look equal, type-wise, at runtime, so that:
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)) }
}
gives the following compiler error:
<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)
) }
^
But is there a simple way to be able to write:
bar.foo(1, 2, 3)
bar.foo(1 -> 2, 3 -> 4)
and having these call different overloaded versions of foo, without having to explicitly name them:
bar.fooInts(1, 2, 3)
bar.fooPairs(1 -> 2, 3 -> 4)
You can, in a fairly round about way. Foo
is a type class, and the compiler implcitly passes an instance of the type class, compatible with the (inferred) type parameter 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)])
In the second call, both FooAny
and FooTuple2
could be passed, but the compiler picks FooTuple2
, based on the rules of static method overloading. FooTuple2
is considered more specific that FooAny
. If two candidates are considered to be as specific as each other, an ambiguity error is raised. You can also prefer one over the other by placing one in a superclass, as is done in scala.LowPriorityImplicits
.
UPDATE
Riffing off the DummyImplicit idea, and the followup thread on 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"))
This declares a type-parameterized trait __
, covariant in its unnamed type parameter _
. Its companion object __
contains an implicit instance of __[Any]
, which we'll need later on. The second and third overloads of foo
include a dummy type parameters, again unnamed. This will be inferred as Any
. This type parameter has one or more context bounds, which are desugared into additional implicit parameters, for example:
def foo[A](a: Seq[Int])(implicit ev$1: __[A]) = 1
The multiple parameter lists are concatenated into a single parameter list in the bytecode, so the double definition problem is circumvented.
Please consider this as a opportunity to learn about erasure, context bounds and implicit search, rather than as a pattern to be applied in real code!
class Bar {
def foo[A](xs: A*) { xs.foreach{
case (a,b) => println(a + " - " + b)
case a => println(a)}
}
}
This will allow
bar.foo(1,2)
bar.foo(1->3,2->4)
But also allow
bar.foo(1->2,5)
If you don't mind loosing the possibility of calling foo with zero arguments (an empty Seq, if you like), then this trick can help:
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)) }
I can not check if it works now (not even if it compiles), but I think the main idea is quite easy to understand: the type of the first parameter won't be erased, so the compiler can make the difference based on that.
Unfortunately it's also not very convenient if you already have a Seq and you want to pass it to foo.
This seems less complicated than retronym's method, and is a slightly less verbose (albeit less general) version of 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))
}
}
This technique can also be used if you have two overloads with the same number of type parameters:
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)
}
}
There is another hacky way to get this working: Glue an unrelated implicit argument on one of the methods:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With