Scala中泛型中的Any和下划线有何不同?

83
以下Scala中泛型定义的区别是什么:
class Foo[T <: List[_]]

class Bar[T <: List[Any]]

我的直觉告诉我它们大致相同,但后者更加明确。我发现有些情况前者可以编译通过,而后者却不能,但我无法确定具体区别。
谢谢!
编辑:
我能加入另一个吗?
class Baz[T <: List[_ <: Any]]

7
将一个类型限制为 <: Any 不会改变任何东西。在 Scala 中,每个类型都是 <: Any - Randall Schulz
2个回答

107

好的,我想我应该发表自己的看法,而不仅仅是发表评论。如果你只想看简短版,请跳到结尾。抱歉,这篇文章会比较长。

正如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))。是的,无法编译。只需将compileAndRun泛型化为A即可使代码编译,并且会更简单。
简而言之,这可能不是学习存在性及其用途的最佳文章(作者本人在评论中承认文章“需要整理”)。
因此,我更愿意推荐阅读这篇文章: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在此处不可见。

好的,那么让我们将该方法改为泛型方法:

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

太好了,这可行。我们还可以在这里使用存在论:

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

同样有效。总的来说,在使用存在类型(如sayHi3)和类型参数(如sayHi2)方面,没有真正的好处。

然而,如果Greets本身作为另一个泛型类的类型参数出现,则情况会有所改变。例如,我们想在列表中存储多个不同TGreets实例。让我们试一试:

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)

现在,请记住我们在第一次遇到类型检查问题的原因是因为Greets是不变的。如果将其转换为协变类(class Greets[+T]),那么一切都会顺利进行,我们永远不需要存在性。
总之,存在性对于处理通用不变类非常有用,但是如果通用类不需要本身作为另一个通用类的类型参数出现,则很可能不需要存在性,只需将类型参数添加到方法中即可。
现在回到你的具体问题。关于
class Foo[T <: List[_]]

由于List是协变的,因此这与仅仅说:

实际上是相同的。
class Foo[T <: List[Any]]

因此,在这种情况下,使用任一符号只是一个风格问题。

然而,如果你将List替换为Set,情况就会改变:

class Foo[T <: Set[_]]

Set 是不变的,因此我们面临着与我例子中的 Greets 类相同的情况。因此,以上内容与之前的内容确实非常不同。

class Foo[T <: Set[Any]]

1
不,class Foo[T <: List[_]]class Foo[T <: List[Z] forSome { type Z }] 的简写。同样,List[Greets[_]]List[Greets[Z] forSome { type Z }] 的简写(而不是 List[Greets[Z]] forSome { type Z })。 - Jesper Nordenberg
哎呀,我真是太傻了!谢谢,我已经修复了这个问题。有趣的是,我的第一个想法是检查David R MacIver的文章(http://www.drmaciver.com/2008/03/existential-types-in-scala/),他正好谈到了存在类型的简写,并警告它们的不直观展开方式。问题在于,他自己似乎也搞错了。实际上,展开方式在他的文章之后不久就发生了改变(在scala 2.7.1中,详见更改日志http://www.scala-lang.org/node/43#2.8.0)。我想这种改变可能导致了混淆。 - Régis Jean-Gilles
嗯,很容易混淆存在类型语法的含义。至少目前的展开是我认为最合乎逻辑的。 - Jesper Nordenberg
class Foo[T <: List[Z forSome { type Z }] 中,最后一个 ] 应该放在哪里? - joel
确实是这样,末尾缺少了一个]。应该是class Foo[T <: List[Z forSome { type Z }]]。现在已经修复了,谢谢。 - Régis Jean-Gilles

7

这里的“former”是指存在类型的简写形式,当代码不需要知道类型或对其进行约束时使用:

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

这个表单表示List元素类型对于class Foo是未知的,而不是第二种形式,它明确指出List的元素类型是Any
请查看关于Scala中存在类型的简要解释性博客文章编辑:此链接已失效,在archive.org上有快照)。

3
当你解释什么是存在类型时,我认为问题不在于存在类型的一般性质,而在于T[_](这是存在类型使用的特殊情况)和T[Any]之间是否存在任何实际可观察的区别? - Régis Jean-Gilles
当然有。我提到的博客有一个很好的插图。 - Randall Schulz
3
我更希望你的回答提到协变性对于解释 T[_]T[Any] 的区别至关重要,因为它是问题“为什么甚至要使用 T[_] 而不是 T[Any]”的核心。此外,在他的问题中,Sean Connolly明确提到了 List[_]。考虑到 List 实际上是协变的,人们可以想知道在这种情况下是否真的存在 List[_]List[Any] 之间的区别。 - Régis Jean-Gilles
2
如果 T 是协变的,并且其类型参数的上界为 Any,那么 T[_]T[Any] 之间似乎没有任何区别。但是,如果你有 T[+A <: B],编译器无法从 T[_] 推断出 T[_ <: B],这看起来很奇怪。 - Jesper Nordenberg
是的,确实在末尾缺少了一个],现在已经修复了。 - Régis Jean-Gilles
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接