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

Sea*_*lly 68 generics scala covariance existential-type any

Scala中以下泛型定义之间有何不同:

class Foo[T <: List[_]]
Run Code Online (Sandbox Code Playgroud)

class Bar[T <: List[Any]]
Run Code Online (Sandbox Code Playgroud)

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

谢谢!

编辑:

我可以把另一个扔进去吗?

class Baz[T <: List[_ <: Any]]
Run Code Online (Sandbox Code Playgroud)

Rég*_*les 92

好吧,我想我应该接受它,而不仅仅是发表评论.对不起,如果你想要TL,这将会很长; DR跳到最后.

正如兰德尔舒尔茨所说,这_是一个存在主义类型的简写.也就是说,

class Foo[T <: List[_]]
Run Code Online (Sandbox Code Playgroud)

是一个简写

class Foo[T <: List[Z] forSome { type Z }]
Run Code Online (Sandbox Code Playgroud)

请注意,与Randall Shulz的回答提到的相反(完全披露:我在本文的早期版本中也错了,感谢Jesper Nordenberg指出它)这不同于:

class Foo[T <: List[Z]] forSome { type Z }
Run Code Online (Sandbox Code Playgroud)

也不是一样的:

class Foo[T <: List[Z forSome { type Z }]]
Run Code Online (Sandbox Code Playgroud)

要注意,很容易弄错(正如我之前的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
}
Run Code Online (Sandbox Code Playgroud)

这个类是通用的(注意也是不变的),但是我们可以看到它hello实际上没有使用类型参数(不像getName),所以如果我得到一个Greets我应该总是能够调用它的实例,无论如何T是.如果我想定义一个接受Greets实例并只调用其hello方法的方法,我可以试试这个:

def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
Run Code Online (Sandbox Code Playgroud)

果然,这不会编译,因为T这里无处可寻.

那么,让我们使方法通用:

def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
Run Code Online (Sandbox Code Playgroud)

太棒了,这很有效.我们也可以在这里使用存在感:

def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
Run Code Online (Sandbox Code Playgroud)

也工作.总而言之,使用存在性(as in sayHi3)over类型参数(如in 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
Run Code Online (Sandbox Code Playgroud)

最后一行不能编译,因为它Greets是不变的,所以a Greets[String]Greets[Symbol]不能被视为Greets[Any]偶数,String并且Symbol两者都延伸Any.

好吧,让我们尝试一下存在主义,使用速记符号_:

val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah
Run Code Online (Sandbox Code Playgroud)

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

greetsSet foreach (_.hello)
Run Code Online (Sandbox Code Playgroud)

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


总而言之,存在性对于处理泛型不变类很有用,但是如果泛型类不需要将自身显示为另一个泛型类的类型参数,那么很可能你不需要存在而只需添加一个类型参数你的方法会奏效

现在回到(最后,我知道!)你的具体问题,关于

class Foo[T <: List[_]]
Run Code Online (Sandbox Code Playgroud)

因为List是协变的,所有意图和目的都只是说:

class Foo[T <: List[Any]]
Run Code Online (Sandbox Code Playgroud)

所以在这种情况下,使用任何一种符号都只是风格问题.

但是,如果更换ListSet,事情的变化:

class Foo[T <: Set[_]]
Run Code Online (Sandbox Code Playgroud)

Set是不变的,因此我们处于与Greets我的例子中的类相同的情况.因此上面的确非常不同

class Foo[T <: Set[Any]]
Run Code Online (Sandbox Code Playgroud)


Ran*_*ulz 6

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

class Foo[T <: List[Z forSome { type Z }]]
Run Code Online (Sandbox Code Playgroud)

这个表单表示元素类型List是未知的,class Foo而不是你的第二个形式,具体说明了List元素的类型Any.

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

  • 当你解释存在主义是什么时,我认为问题不在于存在性是什么,而是存在于`T [_]`(存在主义使用的特殊情况)和`T [Any]之间的任何实际可观察​​的差异. ? (3认同)
  • 我宁愿你**你的答案提到协方差对于'T [_]`和`T [Any]`之间的区别至关重要,因为它是问题的核心"为什么甚至使用`T [ _]`over`T [Any]`".另外,在他的问题中,肖恩·康诺利明确提到了"列表[_]".鉴于`List`实际上是协变的,人们可能想知道**在这种情况下**是否真的存在`List [_]`和`List [Any]`之间的区别. (3认同)
  • 如果T是协变**并且**它的类型参数有'Any`作为其上限,那么`T [_]`和`T [Any]`之间似乎没有任何区别.但是,如果你有'T [+ A <:B]`,那么奇怪的编译器就不能从`T [_]`推断出`T [_ <:B]`. (2认同)