Как я могу различить def foo[A](xs: A*) и 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)) }
}
выдает следующую ошибку компилятора:
<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)
) }
^
Но есть ли простой способ написать:
bar.foo(1, 2, 3)
bar.foo(1 -> 2, 3 -> 4)
и они вызывают разные перегруженные версии foo, без необходимости явно называть их:
bar.fooInts(1, 2, 3)
bar.fooPairs(1 -> 2, 3 -> 4)
6 ответов
Вы можете, довольно круглым способом. Foo
является классом типа, и компилятор неявно передает экземпляр класса типа, совместимый с (выводимым) параметром типа 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)])
Во втором звонке оба FooAny
а также FooTuple2
может быть передан, но компилятор выбирает FooTuple2
на основе правил перегрузки статического метода. FooTuple2
считается более конкретным, что FooAny
, Если два кандидата считаются такими же конкретными, как и другие, возникает ошибка неоднозначности. Вы также можете отдать предпочтение одному над другим, поместив один в суперкласс, как это сделано в scala.LowPriorityImplicits
,
ОБНОВИТЬ
Отказ от идеи DummyImplicit и последующего потока на 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"))
Это объявляет типизированную черту __
, ковариантный по своему параметру безымянного типа _
, Его спутник __
содержит неявный экземпляр __[Any]
, который нам понадобится позже. Вторая и третья перегрузки foo
включить фиктивные параметры типа, снова безымянный. Это будет выведено как Any
, Этот параметр типа имеет одну или несколько границ контекста, которые разбиты на дополнительные неявные параметры, например:
def foo[A](a: Seq[Int])(implicit ev$1: __[A]) = 1
Несколько списков параметров объединяются в один список параметров в байт-коде, поэтому проблема двойного определения обходится.
Пожалуйста, рассмотрите это как возможность узнать об удалении, контекстных границах и неявном поиске, а не как шаблон, который будет применяться в реальном коде!
В случае, когда у нас есть только 2 перегрузки, мы можем упростить ответ Landei и избежать необходимости определять наши собственные неявные, используя scala.Predef.DummyImplicit
который автоматически импортируется в каждую область для вас.
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))
}
}
Если вы не против потерять возможность вызова foo с нулевыми аргументами (пустой Seq, если хотите), то этот прием может помочь:
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)) }
Я не могу проверить, работает ли он сейчас (даже если он компилируется), но я думаю, что основная идея довольно проста для понимания: тип первого параметра не будет удален, поэтому компилятор может изменить ситуацию, основываясь на этом.,
К сожалению, это также не очень удобно, если у вас уже есть Seq, и вы хотите передать его в foo.
class Bar {
def foo[A](xs: A*) { xs.foreach{
case (a,b) => println(a + " - " + b)
case a => println(a)}
}
}
Это позволит
bar.foo(1,2)
bar.foo(1->3,2->4)
Но также позволяют
bar.foo(1->2,5)
Это кажется менее сложным, чем метод retronym, и является несколько менее подробной (хотя и менее общей) версией решения DummyImplicit Кена Блума:
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))
}
}
Эту технику также можно использовать, если у вас есть две перегрузки с одинаковым количеством параметров типа:
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)
}
}
Есть еще один хакерский способ заставить это работать: склеить несвязанный неявный аргумент с одним из методов:
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