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


76

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

class Foo[T <: List[_]]

class Bar[T <: List[Any]]

我的直觉告诉我它们大致相同,但后者更明确。我发现前者可以编译但后者不能编译的情况,但不能使我完全了解。

谢谢!

编辑:

我可以混入另一个吗?

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

7
将类型约束为<: Any从不改变任何内容。Scala中的每种类型都是<: Any
兰德尔·舒尔茨

Answers:


101

好的,我认为我应该对此负责,而不仅仅是发表评论。抱歉,如果您想让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”和“ Java and Scala中的差异”的部分。

从本文中可以得出的重要一点是,在处理非协变量类型时,存在性是有用的(除了能够处理通用的Java类)。这是一个例子。

case class Greets[T]( private val name: T ) {
  def hello() { println("Hello " + name) }
  def getName: T = name
}

此类是通用的(也请注意,它是不变的),但是我们可以看到它hello实际上并没有使用type参数(不同于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是不变的,因此aGreets[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]]

因此,在这种情况下,使用任何一种表示方式实际上只是样式问题。

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

class Foo[T <: Set[_]]

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

class Foo[T <: Set[Any]]

1
不,class Foo[T <: List[_]]是的简写class Foo[T <: List[Z] forSome { type Z }]。相同的方式List[Greets[_]]List[Greets[Z] forSome { type Z }](而不是List[Greets[Z]] forSome { type Z })的简写。
Jesper Nordenberg

h,愚蠢的我!谢谢,我已解决此问题。有趣的是,我的第一篇文章是查看David R MacIver(drmaciver.com/2008/03/existential-types-in-scala)的文章,该文章恰好谈到了存在的简写,并警告说它们存在不直观的贬义。问题是他似乎自己错了。实际上,发生的事情是在他的文章发表后不久就完成了改制的方式(在scala 2.7.1中,请参见scala-lang.org/node/43#2.8.0上的更改日志)。我想这种变化会引起混乱。
雷吉斯让吉尔斯

好吧,很容易混淆存在类型语法的含义。至少当前的退货是最合乎逻辑的恕我直言。
Jesper Nordenberg

最后的]去哪儿了class Foo[T <: List[Z forSome { type Z }]
joel

是的,的确是这样,]末尾有一个失踪者。那应该是class Foo[T <: List[Z forSome { type Z }]]。立即修复,谢谢。
雷吉斯让吉尔斯

7

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

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

这种形式说的元素类型List是未知的class Foo,而不是你的第二个形式,具体的说List的元素类型Any

请查看有关Scala中的现有类型的简短说明性博客文章编辑:此链接现已失效,可以在archive.org上获取快照)


3
当您解释什么是存在物时,我认为问题不是一般的存在物,而是T[_](存在性使用的特殊情况)与T[Any]
雷吉斯让吉尔斯

当然有 我提到的博客对此进行了很好的说明。
兰德尔·舒尔茨

3
我希望您的回答提及协方差对于T[_]和之间的差异至关重要T[Any],因为协方差是“为什么还要使用T[_] over T[Any]”问题的核心。另外,肖恩·康诺利(Sean Connolly)在他的问题中明确提到List[_]。鉴于这List实际上是协变的,所以您可能想知道在这种情况下List[_]和之间是否确实存在差异List[Any]
雷吉斯让吉尔斯

2
如果T是协变的并且其类型参数具有Any上限,则T[_]和之间似乎没有任何区别T[Any]。但是,如果您有T[+A <: B]编译器,那么奇怪的是无法T[_ <: B]从中推断出T[_]
Jesper Nordenberg

@Jesper Nordenberg:实际上,这种推断在scala 2.8之前的版本中有效,但在scala 2.8中被“破坏了”。实际上,这是有目的的,因为这显然与其余类型系统玩得很烂,并且不一致(尽管我不记得详细信息)。
雷吉斯让吉尔斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.