80

Scala中的以下泛型定义有什么不同:

class Foo[T <: List[_]]

class Bar[T <: List[Any]]

我的直觉告诉我它们大致相同,但后者更明确。我发现前者编译但后者没有编译的情况,但我无法确定确切的区别。

谢谢!

编辑:

我可以再加入一个吗?

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

2 回答 2

106

好吧,我想我应该接受它,而不是仅仅发表评论。抱歉,这会很长,如果您希望 TL;DR 跳到最后。

正如 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 ,特别是名为“Existential types”和“Variance in Java and 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))

太好了,这行得通。我们也可以在这里使用existentials:

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

也可以。所以总而言之,在这里使用存在(如sayHi3)而不是类型参数(如sayHi2)并没有真正的好处。

但是,如果Greets它本身作为另一个泛型类的类型参数出现,则会发生变化。举例来说,我们想在一个列表中存储Greets(with different ) 的几个实例。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它是不变的,因此 aGreets[String]Greets[Symbol]不能被视为 aGreets[Any]即使StringSymbol都 extends Any

好的,让我们尝试使用简写符号_

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

这编译得很好,你可以按预期做:

greetsSet foreach (_.hello)

现在,请记住,我们首先遇到类型检查问题的原因是因为Greets它是不变的。如果它变成一个协变类 ( class Greets[+T]),那么一切都会开箱即用,我们将永远不需要存在主义。


总而言之,existentials 对于处理泛型不变类很有用,但是如果泛型类不需要将自身作为类型参数出现在另一个泛型类中,那么您可能不需要存在主义并且只需添加一个类型参数你的方法会起作用

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

class Foo[T <: List[_]]

因为List是协变的,所以这与刚才说的所有意图和目的相同:

class Foo[T <: List[Any]]

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

但是,如果您替换ListSet,情况会发生变化:

class Foo[T <: Set[_]]

SetGreets是不变的,因此我们与我的示例中的类处于相同的情况。因此,上述内容确实与

class Foo[T <: Set[Any]]
于 2013-03-04T14:42:59.237 回答
7

当代码不需要知道类型是什么或对其进行约束时,前者是存在类型的简写:

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

此表单表示 of 的元素类型List是未知的,class Foo而不是您的第二种形式,后者明确表示List的元素类型为Any

查看这篇关于 Scala 中存在类型的简短解释性博客文章(编辑:此链接现已失效,可在archive.org上获得快照)

于 2013-03-03T14:43:57.487 回答