2013-03-03 62 views
49

下划线什么是Scala以下泛型定义之间的不同:斯卡拉 - 任何VS仿制药

class Foo[T <: List[_]] 

class Bar[T <: List[Any]] 

我的直觉告诉我,他们是差不多的,但后者更明确。我发现前者编译但后者不编译的情况,但不能把我的手指放在确切的区别上。

谢谢!

编辑:

我可以抛出另一个混进去?

class Baz[T <: List[_ <: Any]] 
+5

将类型约束为'<:Any'永远不会改变任何东西。 Scala中的每个类型都是<<:Any'。 – 2013-03-03 19:23:35

回答

64

好的,我想我应该把它拿起来,而不是仅仅发表评论。对不起,如果你想让TLDR跳转到最后,这将会很长。

正如Randall Schulz所说,这里的_是存在型的简写。也就是说,

class Foo[T <: List[_]] 

class Foo[T <: List[Z] forSome { type Z }] 

注意的简写是相反的是兰德尔Shulz的回答中提到(全面披露:我听错了,太早期版本FO这个帖子,感谢加斯帕Nordenberg为它指向了)这种不一样:

class Foo[T <: List[Z]] forSome { type Z } 

也不是一样的:

class Foo[T <: List[Z forSome { type Z }] 

要小心,很容易弄错它(正如我之前的愚蠢表演):Randall Shulz的回答引用的文章的作者自己弄错了(见评论),并在稍后修复。这篇文章的主要问题是,在所示的例子中,使用existentials应该可以让我们免于打字问题,但事实并非如此。去检查代码,并尝试编译compileAndRun(helloWorldVM("Test"))compileAndRun(intVM(42))。是的,不编译。只需在A中编写compileAndRun就可以编译代码,并且它会简单得多。 总之,这可能不是最好的文章来了解存在和它们的优点(作者本人在评论中承认文章“需要整理”)。

所以我宁愿推荐阅读这篇文章:http://www.artima.com/scalazine/articles/scalas_type_system.html,特别是名为“存在类型”和“Java和Scala中的差异”的章节。

你应该从这篇文章中得到的重要一点是,在处理非协变类型时,存在是有用的(除了能够处理泛型java类)。 这里是一个例子。

case class Greets[T](private val name: T) { 
    def hello() { println("Hello " + name) } 
    def getName: T = name 
} 

这个类是通用的(注意也即是不变的),但我们可以看到,hello真的不使用类型参数的(不像getName),所以如果我得到Greets我的一个实例应该始终能够调用它,无论是T是。如果我想定义需要Greets实例的方法,只是调用其hello方法,我可以试试这个:

def sayHi1(g: Greets[T]) { g.hello() } // Does not compile 

果然,这不能编译,因为T出来的无处这里。然后

OK,让我们的方法一般:

def sayHi2[T](g: Greets[T]) { g.hello() } 
sayHi2(Greets("John")) 
sayHi2(Greets('Jack)) 

大,这个工程。我们也可以在这里使用existentials:

def sayHi3(g: Greets[_]) { g.hello() } 
sayHi3(Greets("John")) 
sayHi3(Greets('Jack)) 

也可以。因此,总而言之,在类型参数(如sayHi2)中使用存在(如sayHi3)没有真正的好处。

但是,如果Greets本身作为另一个泛型类的类型参数出现,则会发生变化。举例来说,我们想要在列表中存储Greets(不同的T)的多个实例。让我们试一下:

val greets1: Greets[String] = Greets("John") 
val greets2: Greets[Symbol] = Greets('Jack) 
val greetsList1: List[Greets[Any]] = List(greets1, greets2) // Does not compile 

最后一行不编译,因为Greets是不变的,所以Greets[String]Greets[Symbol]不能作为Greets[Any]即使StringSymbol两个延伸Any处理。

OK,让我们尝试用一个存在,用速记符号_

val greetsList2: List[Greets[_]] = List(greets1, greets2) // Compiles fine, yeah 

编译没有问题,你可以做,如预期:

greetsSet foreach (_.hello) 

现在,请记住原因首先我们有一个类型检查问题,因为Greets是不变的。如果它变成了一个协变类(class Greets[+T]),那么所有东西都可以在盒子里运行,我们永远不会需要存在。


所以总结起来,existentials是有用的处理一般不变类,但如果通用类并不需要单独出现的类型参数到另一个泛型类,有机会,你不需要existentials和简单地增加一个类型参数的方法,将工作

现在回来(在最后,我知道!)你的具体问题,关于

class Foo[T <: List[_]] 

因为List是协变的,这是所有意图和purp其他只是说:

class Foo[T <: List[Any]] 

所以在这种情况下,使用任何符号实际上只是一个风格问题。

但是,如果你Set替换List,事情的变化:

class Foo[T <: Set[_]] 

Set是不变的,因此我们在相同的情况下从我的例子Greets类。因此,上述确实与

有很大不同
class Foo[T <: Set[Any]] 
+0

不,类Foo [T <:List [_]]'是类Foo [T <:List [Z] forSome {type Z}]'的缩写。 'List [Greets [_]]'是'List [Greets [Z] forSome {type Z}]'(而不是'List [Greets [Z]] for some {type Z}'')的简写。 – 2013-03-06 11:58:02

+0

Doh,傻我!谢谢,我已经解决了这个问题。有趣的是,我的第一个尝试是检查David R MacIver的文章(http://www.drmaciver.com/2008/03/existential-types-in-scala/),它精确地谈论了存在速记,并警告说他们不直观的desugaring。事情是他自己看起来错了。实际上,发生的事情是,在他的文章(scala 2.7.1,请参阅http://www.scala-lang.org/node/43#2.8.0上的更改日志)后不久,desugaring的方式发生了变化。我想这个改变会导致混乱。 – 2013-03-06 13:10:18

+0

好吧,很容易混淆存在类型语法的含义。至少目前的desugaring是最符合逻辑的恕我直言。 – 2013-03-06 14:03:43

6

前者是一个存在的类型的速记时的代码并不需要知道是什么类型或限制它:

class Foo[T <: List[Z forSome { type Z }] 

这种形式说的List元素类型未知的class Foo而不是你的第二种形式,具体说明List的元素类型是Any

查看关于Scala存在类型的简短说明blog article

+3

当你解释什么是存在的时候,我认为这个问题不是一般存在的问题,而是存在'T [_]'(这是存在使用的特殊情况)和'T [Any] '? – 2013-03-03 15:58:18

+0

当然有。我提到的博客有一个很好的例子。 – 2013-03-03 17:30:52

+3

我宁愿让你的**答案提到协方差对于'T [_]'和'T [Any]'之间的区别是如何必不可少的,因为它是问题的核心“为什么要用'T [_]'在T [Any]'上方''“。另外,在他的问题中,肖恩·康诺利明确提到了“列表[_]”。鉴于'List'实际上是协变的,人们可能会怀疑在这种情况下**是否真的存在'List [_]'和'List [Any]之间的区别。 – 2013-03-03 18:22:09