2010-08-06 3 views
14

Я знаю, что тип стирание делает их равными, тип-накрест, во время выполнения, так что:Как я могу различать 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) 

и с их вызова различных перегруженных версий обув, без явного их назвать:

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

Самый простой способ сделать это с помощью связанного с контекстом ClassManifest для каждого из параметров типа: 'def foo [A : ClassManifest] (xs: A *) ... '. Я добавил это как ответ с дополнительными комментариями ниже. –

+0

При перегрузке учитывать: http://stackoverflow.com/questions/2510108/why-avoid-method-overloading –

+0

См. Также: http://stackoverflow.com/questions/3307427/scala-double-definition-2-methods- стирать-то же типа – retronym

ответ

15

Вы можете, в довольно круглом порядке. 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 идею, а продолжительность нить на лестницу-пользователь:

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 

множественных списков параметров объединяются в один список параметров в байткоде, так что проблема двойного определения обойдена ,

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

+0

Это выглядит великолепно, но вы возвращаетесь к Единице там ... Что мы ограничиваемся этим возвратом? Должны ли мы иметь возможность инвертировать типы только из заявления об этом? – dividebyzero

3
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) 
4

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

+0

На самом деле это довольно хороший хак. Btw, вам нужно сделать (x :: xs.toList) .foreach (...). –

+0

Спасибо за исправление, я исправил его. –

2

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

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 
8

В том случае, когда мы имеем только 2 перегрузкам, мы можем упростить Landei's answer и избежать необходимо определить наш собственный неявный, используя 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)) 
    } 
} 
+1

На самом деле это не ограничивается двумя перегрузками. Он работает с любым количеством перегрузок, если каждая перегрузка имеет другое количество параметров DummyImplicit. –

+0

Думал, что вам может быть интересно - я опубликовал об этом в списке scala-users: http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic -type-erasure-td2327664.html –

+0

Однострочное исправление (на самом деле, всего два слова), это действительно мило. Удивление того, что «правильное» исправление Scala должно было бы избежать необходимости обходного пути. – bjfletcher

3

Это кажется менее сложным, чем retronym's method, и немного менее многословным (хотя и менее общий) версия 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)) 
    } 
} 

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

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

Я не думаю, что это более общее, потому что это зависит от того, что разные перегрузки имеют разные числа общих параметров. Этот метод не будет работать для двусмысленности между 'foo (xs: Int *)' и 'foo (xs: String *)'. –

+0

Это более общее в том смысле, что это не ограничивается 2 перегрузками. Он также охватывает любой набор из 2 перегрузок при условии, что вы опускаете контекст, связанный с одной из перегрузок. –

+0

Я предполагаю, что он работает в любой ситуации, когда вы можете передавать разные номера «ClassManifests». С другой стороны, вы можете сделать это с разными номерами «DummyImplicits». –

Смежные вопросы