Scala中以下泛型定义之间有何不同:
class Foo[T <: List[_]]
和
class Bar[T <: List[Any]]
我的直觉告诉我他们差不多,但后者更明确。我发现前者编译的情况,但后者不编,但不能指出确切的区别。
谢谢!
编辑:
我可以把另一个扔进去吗?
class Baz[T <: List[_ <: Any]]
好吧,我想我应该接受它,而不仅仅是发表评论。对不起,如果你想要TL,这将会很长; DR跳到最后。
正如兰德尔舒尔茨所说,这里_
是存在主义类型的简写。也就是说,
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
都延伸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]]
前者是存在类型的简写,当代码不需要知道类型是什么或约束它时:
class Foo[T <: List[Z forSome { type Z }]]
这个形式说List
的元素类型不是class Foo
而不是你的第二种形式,它具体说明List
的元素类型是Any
。
查看关于Scala中存在类型的简短解释blog article(编辑:此链接现已死亡,快照可在archive.org获得)