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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15186520/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:01:46  来源:igfitidea点击:

scala - Any vs underscore in generics

scalagenericscovarianceanyexistential-type

提问by Sean Connolly

What is the different between the following Generics definitions in Scala:

Scala 中的以下泛型定义之间有什么不同:

class Foo[T <: List[_]]

and

class Bar[T <: List[Any]]

My gut tells me they are about the same but that the latter is more explicit. I am finding cases where the former compiles but the latter doesn't, but can't put my finger on the exact difference.

我的直觉告诉我它们大致相同,但后者更明确。我正在发现前者编译但后者不编译的情况,但我无法指出确切的区别。

Thanks!

谢谢!

Edit:

编辑:

Can I throw another into the mix?

我可以再加入一个吗?

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

回答by Régis Jean-Gilles

OK, I figured I should have my take on it, instead of just posting comments. Sorry, this is going to be long, if you want the TL;DR skip to the end.

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

As Randall Schulz said, here _is a shorthand for an existential type. Namely,

正如兰德尔舒尔茨所说,这_是存在类型的简写。即,

class Foo[T <: List[_]]

is a shorthand for

是一个简写

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

Note that contrary to what Randall Shulz's answer mentions (full disclosure: I got it wrong too in an earlier version of this post, thanks to Jesper Nordenberg for pointing it out) this not the same as:

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

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

nor is it the same as:

也不等同于:

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

Beware, it is easy to get it wrong (as my earlier goof shows): the author of the article referenced by Randall Shulz's answer got it wrong himself (see comments), and fixed it later. My main problem with this article is that in the example shown, the use of existentials is supposed to save us from a typing problem, but it does not. Go check the code, and try to compile compileAndRun(helloWorldVM("Test"))or compileAndRun(intVM(42)). Yep, does not compile. Simply making compileAndRungeneric in Awould make the code compile, and it would be much simpler. In short, that's probably not the best article to learn about existentials and what they are good for (the author himself acknowledge in a comment that the article "needs tidying up").

当心,很容易弄错(正如我之前的错误所示):Randall Shulz 的回答所引用的文章的作者自己弄错了(见评论),后来修复了它。我对这篇文章的主要问题是,在显示的示例中,使用existentials 应该可以使我们免于打字问题,但事实并非如此。去检查代码,然后尝试编译compileAndRun(helloWorldVM("Test"))compileAndRun(intVM(42)). 是的,不会编译。简单地使compileAndRun泛型 inA将使代码编译,并且它会简单得多。简而言之,这可能不是了解存在主义及其好处的最佳文章(作者本人在评论中承认该文章“需要整理”)。

So I would rather recommend reading this article: http://www.artima.com/scalazine/articles/scalas_type_system.html, in particular the sections named "Existential types" and "Variance in Java and Scala".

所以我更愿意推荐阅读这篇文章:http: //www.artima.com/scalazine/articles/scalas_type_system.html,特别是名为“Existential types”和“Variance in Java and Scala”的部分。

The important point that you hould get from this article is that existentials are useful (apart from being able to deal with generic java classes) when dealing with non-covariant types. Here is an example.

您应该从本文中了解到的重要一点是,在处理非协变类型时,存在项是有用的(除了能够处理泛型 java 类之外)。这是一个例子。

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

This class is generic (note also that is is invariant), but we can see that helloreally doesn't make any use of the type parameter (unlike getName), so if I get an instance of GreetsI should always be able to call it, whatever Tis. If I want to define a method that takes a Greetsinstance and just calls its hellomethod, I could try this:

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

def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile

Sure enough, this does not compile, as Tcomes out of nowhere here.

果然,这不会编译,因为T这里无处不在。

OK then, let's make the method generic:

好的,让我们使方法通用:

def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Hyman))

Great, this works. We could also use existentials here:

太好了,这有效。我们也可以在这里使用existentials:

def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Hyman))

Works too. So all in all, there is no real benefit here from using an existential (as in sayHi3) over type parameter (as in sayHi2).

也有效。因此,总而言之,sayHi3在类型参数(如 )上使用存在(如)并没有真正的好处sayHi2

However, this changes if Greetsappears itself as a type parameter to another generic class. Say by example that we want to store several instances of Greets(with different T) in a list. Let's try it:

但是,如果Greets它本身作为另一个泛型类的类型参数出现,则会发生变化。举个例子,我们想在一个列表中存储Greets(不同的T)的几个实例。让我们试试看:

val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Hyman)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile

The last line does not compile because Greetsis invariant, so a Greets[String]and Greets[Symbol]cannot be treated as a Greets[Any]even though Stringand Symbolboth extends Any.

最后一行不会编译,因为它Greets是不变的,所以 a Greets[String]andGreets[Symbol]不能被视为 a Greets[Any]even ifStringSymbolboth extends Any

OK, let's try with an existential, using the shorthand notation _:

好的,让我们尝试一个存在,使用速记符号_

val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah

This compiles fine, and you can do, as expected:

这编译得很好,您可以按预期进行:

greetsSet foreach (_.hello)

Now, remember that the reason we had a type checking problem in the first place was because Greetsis invariant. If it was turned into a covariant class (class Greets[+T]) then everything would have worked out of the box and we would never have needed existentials.

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



So to sum up, existentials are useful to deal with generic invariant classes, but if the generic class does not need to appear itself as a type parameter to another generic class, chances are that you don't need existentials and simply adding a type parameter to your method will work

所以总而言之,existentials 对于处理泛型不变类很有用,但是如果泛型类不需要将自身作为另一个泛型类的类型参数出现,那么很可能你不需要existentials 并简单地添加一个类型参数你的方法会起作用

Now come back(at last, I know!) to your specific question, regarding

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

class Foo[T <: List[_]]

Because Listis covariant, this is for all intents and purpose the same as just saying:

因为List是协变的,所以出于所有意图和目的,这与刚才说的一样:

class Foo[T <: List[Any]]

So in this case, using either notation is really just a matter of style.

所以在这种情况下,使用任何一种表示法实际上只是一种风格问题。

However, if you replace Listwith Set, things change:

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

class Foo[T <: Set[_]]

Setis invariant and thus we are in the same situation as with the Greetsclass from my example. Thus the above really is very different from

Set是不变的,因此我们的情况与Greets我示例中的类相同。因此上面的真的很不一样

class Foo[T <: Set[Any]]

回答by Randall Schulz

The former is a shorthand for an existential type when the code doesn't need to know what the type is or constrain it:

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

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

This form says that the element type of Listis unknown to class Foorather than your second form, which says specifically that the List's element type is Any.

此表单表示 的元素类型List未知,class Foo而不是您的第二个表单,后者明确表示List的元素类型为Any

Check out this brief explanatory blog articleon Existential Types in Scala (EDIT: this link is now dead, a snapshot is available at archive.org)

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