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
编译和compileAndRun
generics将使代码编译,而且会更简单。 简而言之,这可能不是学习关于存在的最好的文章,也不是最好的文章(作者自己在评论中承认文章“需要整理”)。
所以我宁愿推荐阅读这篇文章: 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]
即使String
和Symbol
都延伸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]]
所以在这种情况下,使用任何符号都只是一个风格问题。
但是,如果将List
replace为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的简短说明性博客文章 。