scala - 泛型中的任何与下划线 [英] scala - Any vs underscore in generics

查看:27
本文介绍了scala - 泛型中的任何与下划线的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

class Foo[T <: List[_]]

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.

谢谢!

我可以加入另一个吗?

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

推荐答案

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

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.

正如 Randall Schulz 所说,这里的 _ 是存在类型的简写.也就是说,

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

class Foo[T <: List[_]]

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

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

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:

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

也不等同于:

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

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

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 compileAndRun generic in A would 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").

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

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".

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

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.

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

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

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

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

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

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

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

OK then, let's make the method generic:

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

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

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

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

也能用.所以总而言之,在类型参数(如在 sayHi2 中)使用存在项(如在 sayHi3 中)并没有真正的好处.

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).

但是,如果 Greets 本身作为另一个泛型类的类型参数出现,则情况会发生变化.举例来说,我们想要在一个列表中存储多个 Greets(具有不同的 T)实例.我们来试试:

However, this changes if Greets appears 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:

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] 即使 StringSymbol 都扩展了 Any.

The last line does not compile because Greets is invariant, so a Greets[String] and Greets[Symbol] cannot be treated as a Greets[Any] even though String and Symbol both 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)

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

Now, remember that the reason we had a type checking problem in the first place was because Greets is 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.

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

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

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

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

class Foo[T <: List[_]]

因为 List 是协变的,这对于所有的意图和目的来说都和刚才说的一样:

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

class Foo[T <: List[Any]]

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

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

然而,如果你用 Set 替换 List,事情就会改变:

However, if you replace List with Set, things change:

class Foo[T <: Set[_]]

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

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

class Foo[T <: Set[Any]]

这篇关于scala - 泛型中的任何与下划线的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆