2016-11-27 58 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 
相关问题