scala - 泛型中的任何vs下划线

时间:2013-03-03 14:07:33

标签: scala generics covariance any existential-type

Scala中以下泛型定义之间的区别是什么:

class Foo[T <: List[_]]

class Bar[T <: List[Any]]

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

谢谢!

修改

我可以将另一个投入混音吗?

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

2 个答案:

答案 0 :(得分:82)

好的,我想我应该接受它,而不仅仅是发表评论。对不起,如果你想要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 }]]

要注意,很容易弄错(正如我之前的goof节目所示):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本身作为另一个泛型类的类型参数出现,则会发生变化。举例来说,我们希望在列表中存储多个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]String也不能视为Symbol {1}}扩展了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 :(得分:6)

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

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

此表单表示List的元素类型不为class Foo而是第二种形式,具体说明List的元素类型为Any

查看有关Scala中存在类型的简要说明blog article编辑:此链接现已停止,archive.org处可提供快照)