2010-08-06 62 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) 

,並具有這些調用foo的不同重載版本,而無需顯式指定它們:

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

最直接的方法是爲每個類型參數使用上下文綁定的ClassManifest:'def foo [A :ClassManifest](xs:A *)...'。我在下面加入了更多評論的答案。 – 2010-08-12 18:14:45

+0

重載時考慮:http://stackoverflow.com/questions/2510108/why-avoid-method-overloading – 2010-08-17 14:59:38

+0

另請參見:http://stackoverflow.com/questions/3307427/scala-double-definition-2-methods-有同樣類型的擦除 – retronym 2010-08-17 21:57:32

回答

15

可以,在一個相當周圍方式。 Foo是一種類型的類,並且編譯器implcitly傳遞類型的類的一個實例,其中(推斷)類型參數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)]) 

在第二個電話,都FooAnyFooTuple2可以通過,但是編譯器挑選FooTuple2,基於靜態方法重載的規則。 FooTuple2被認爲更具體,FooAny。如果兩個候選人被認爲是彼此特定的,則會產生模糊性錯誤。你也可以選擇一個放在超類中,比如scala.LowPriorityImplicits

UPDATE

Riffing關閉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 2015-05-04 17:19:31

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零個參數(空序列的可能性,如果你喜歡),那麼這個技巧可以幫助:

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

這實際上相當不錯。順便說一句,你必須做(x :: xs.toList).foreach(...)。 – 2010-08-06 17:33:32

+0

感謝您的更正,我已修復它。 – 2010-08-18 07:36:01

2

還有一個哈克的方式來得到這個工作:對的方法之一膠水無關的隱含參數:

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

實際上,這不僅限於2次重載。它適用於任意數量的重載,只要每個重載具有不同數量的DummyImplicit參數。 – 2010-08-13 21:37:59

+0

以爲你可能會感興趣 - 我在scala用戶列表上發佈了這個消息:http://scala-programming-language.1934581.n4.nabble.com/disambiguation-of-double-definition-resulting-from-generic -type-erasure-td2327664.html – 2010-08-17 01:22:26

+0

單線程修復(實際上,只是兩個字),這真的很好。想知道Scala應該有什麼「適當的」修復來避免需要解決方法。 – bjfletcher 2015-06-10 23:47:35

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 *)'之間的歧義。 – 2010-08-12 13:39:19

+0

從更廣泛的意義上說,它不限於2次重載。它還涵蓋了任何一組2重載,前提是您省略了其中一個重載的上下文綁定。 – 2010-08-12 15:00:51

+0

我想它適用於任何可以傳遞不同數量的ClassManifests的情況。另一方面,你也可以用不同數量的'DummyImplicits'來做到這一點。 – 2010-08-13 19:56:00

相關問題