Как я могу различать 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)

Ответы

Ответ 1

Вы можете, довольно кругом. Foo - это класс типа, и компилятор несчетно передает экземпляр класса типа, совместимый с параметром типа (inferred) 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.

UPDATE

Отменив идею 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

В случае, когда у нас есть только 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))
  }
}

Ответ 3

Если вы не возражаете потерять возможность вызова 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.

Ответ 4

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)

Ответ 5

Это кажется менее сложным, чем метод retronym, и является чуть менее подробной (хотя и менее общей) версией Ken Bloom 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))
   }
}

Этот метод можно также использовать, если у вас есть две перегрузки с одинаковым количеством параметров типа:

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

Ответ 6

Существует еще один хакерский способ заставить это работать: склеить несвязанный неявный аргумент по одному из методов:

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