Как я могу различить def foo [A] (xs: A *) и def foo [A, B] (xs: (A, B) *)? - PullRequest
15 голосов
/ 06 августа 2010

Я знаю, что стирание типов заставляет их выглядеть одинаково по типам во время выполнения, так что:

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 ]

15 голосов
/ 06 августа 2010

Вы можете, довольно круглым способом. 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.

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

Несколько списков параметров объединяются в один список параметров в байт-коде, поэтому проблема двойного определения обходится.

Пожалуйста, рассмотрите это как возможность узнать о стирании, контекстных границах и неявном поиске, а не как шаблон, который будет применяться в реальном коде!

8 голосов
/ 11 августа 2010

В случае, когда у нас есть только 2 перегрузки, мы можем упростить ответ Ландея и избежать необходимости определять наше собственное неявное, используя 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))
  }
}
4 голосов
/ 06 августа 2010

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

3 голосов
/ 12 августа 2010

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

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

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
...