2016-11-27 54 views
6

我想知道是否有一個很好的理由使用子類型作爲函數類型參數? 讓我們考慮下面的例子:是否有理由在Scala中使用子類型作爲類型參數?

scala> trait Animal { def sound: String } 
defined trait Animal 

scala> def f1[T <: Animal](a: T) = a.sound 
f1: [T <: Animal](a: T)String 

scala> def f2(a: Animal) = a.sound 
f2: (a: Animal)String 

擁有F1一定的優勢超過F2

+2

如果'f1'需要返回一個'T'或某些類型的引用'T ',那當然。 –

回答

5

我相信在你的例子中沒有優勢。類型參數通常用於連接代碼的不同部分,但有關T的信息由於不匹配任何內容而被有效丟失。考慮另一個例子:

def f1[T <: Animal](a: T) = (a.sound, a) 

def f2(a: Animal) = (a.sound, a) 

現在情況不同,因爲T被傳遞到返回類型:

class Dog extends Animal { def sound = "bow-wow" } 

f1(new Dog) 
//> (String, Dog) = (bow-wow,[email protected]) 

f2(new Dog) 
//> (String, Animal) = (bow-wow,[email protected]) 

在這種情況下,你能想到的f1因爲這是「實例」模板編譯時間並且基於編譯時參數類型有效地生成特定的方法。所以如果你想使用f1(new Dog)其中(String, Dog)是必需的,它將編譯,而f2(new Dog)不會。

2

功能f1f2都非常相似。如果輸出的字節碼,你會在常量看到了池,他們表示爲:

#71 = Methodref   #12.#70  // Main$.f2:(LMain$Animal;)Ljava/lang/String; 
#74 = Methodref   #12.#73  // Main$.f1:(LMain$Animal;)Ljava/lang/String; 

至於字節碼來講,他們是採取Animal作爲參數,並返回String功能。

當你想返回一個特定的T(其中T <: Animal)時,這變得更有趣的一種情況。請記住,字節碼仍然會是相同的,但是在編譯時這給了更多的含義與功率T類型參數:

假設你有:

def f1[T <: Animal](a: T): T = a // silly, I know 
def f2(a: Animal): Animal = a 

你試試這個:

val s: Dog = f1(new Dog()) 
val t: Dog = f2(new Dog()) // NOPE 
val u: Dog = f2(new Dog()).asInstanceOf[Dog] // awkward 

第二行不會編譯,不會犧牲編譯時類型檢查。

+0

有趣的是你的'f1'和'f2'也會被編譯成相同的代碼。類型參數被刪除。 –

+0

是的。編譯後,字節碼將是相同的。類型擦除會使它看起來好像'f1'返回'動物'。我試圖做的一點是編譯時的有用性,當類型參數「重要」時。 – drhr

+0

我之所以提到它是因爲你的開始(我原來是這麼做的)是從「它編譯爲相同的字節碼」開始的。 –

0

在Scala和JVM它們具有以下功能打字規則

S1 -> S2 is subtype of T1 -> T2

if and only if

S1 is subtype of T1 and T2 is subtype of S2

在你的榜樣,

def f1[T <: Animal](a: T): String // T <: Animal -> String 
    def f2(a: Animal): String // Animal -> String 

通過功能打字規則,f1f2

的亞型結論f1f2沒有差異E在實際使用情況

請參考,請訪問以下鏈接

https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Function_types

+0

'f1'和'f2'本身都不具有類型,因爲它們不是函數,它們是方法。另外eta-expansion可以根據你的期望給你不同的結果。嘗試REPL中的'f1 _',你會感到驚訝。 –

1

考慮您的例子,其中f1f2具有相同的輸出型f2功能有f1之前的優勢,如果你想超載方法。

所以對於f1它會給出一個錯誤:

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

class Zoo { 
    def f1[T <: Animal](a: T) = a.sound 
    def f1[T <: Dog](a: T) = "Dog says " + a.sound 
} 

// Exiting paste mode, now interpreting. 

<console>:18: error: method f1 is defined twice 
    conflicting symbols both originated in file '<console>' 
     def f1[T <: Dog](a: T) = "Dog says " + a.sound 
      ^

而與f2它的工作原理:

scala> :paste 
// Entering paste mode (ctrl-D to finish) 

class Zoo { 
    def f2(a: Animal) = a.sound 
    def f2(a: Dog) = "Dog says " + a.sound 
} 

// Exiting paste mode, now interpreting. 

defined class Zoo 
相關問題