scala – 任何与generics中的下划线

Scala中的以下generics定义之间有什么不同:

class Foo[T <: List[_]] 

 class Bar[T <: List[Any]] 

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

谢谢!

编辑:

我可以再投入混合?

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

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

正如兰德尔·舒尔茨(Randall Schulz)所说,这里是生存型的简写。 也就是说,

 class Foo[T <: List[_]] 

是一个简写

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

请注意,与兰德尔·舒尔茨(Randall Shulz)的回答所提到的相反(完全公开:我在这篇文章的早期版本中也有错误,这要归功于Jesper Nordenberg指出的),这不同于:

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

也不是一样的:

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

当心,很容易弄错(正如我以前的混乱节目):Randall Shulz的回答所引用的文章的作者自己弄错了(见评论),稍后再修正。 我在这篇文章中遇到的主要问题是,在所示的示例中,使用存在是为了使我们免于打字问题,但是却不能。 去检查代码,并尝试编译compileAndRun(helloWorldVM("Test"))compileAndRun(intVM(42)) 。 是的,不编译。 简单地在A编译和compileAndRungenerics将使代码编译,而且会更简单。 简而言之,这可能不是学习关于存在的最好的文章,也不是最好的文章(作者自己在评论中承认文章“需要整理”)。

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

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

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

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

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

果然,这不会编译,因为T在这里出现。

那么好吧,让我们通用的方法:

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

也可以。 因此,总而言之,使用存在性(比如sayHi3 )而不是types参数(例如sayHi2 )没有任何实际的好处。

但是,如果Greets本身作为另一个generics类的types参数,则会发生更改。 举例来说,我们想要在列表中存储多个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

好吧,让我们尝试一个存在主义,使用简写符号_

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

这编译好,你可以做,如预期的那样:

 greetsSet foreach (_.hello) 

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


所以总结一下,existentials是有用的处理generics不变类,但是如果generics类不需要把它本身作为另一个generics类的types参数,那么你可能不需要存在,只需要添加一个types参数你的方法将工作

现在回来(终于知道了!)关于你的具体问题

 class Foo[T <: List[_]] 

因为List是协变的,所有的意图和目的都是一样的,只是说:

 class Foo[T <: List[Any]] 

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

但是,如果将Listreplace为Set ,事情就会改变:

 class Foo[T <: Set[_]] 

Set是不变的,因此我们和我的例子中的Greets类一样。 因此上面真的是非常不同的

 class Foo[T <: Set[Any]] 

前者是代码不需要知道types或限制types的存在types的简写:

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

这种forms表示List的元素types对于class Foo是未知的,而不是你的第二种forms,它明确指出List的元素types是Any

看看这个关于Scala存在types的简短说明性博客文章 。