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

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

问题描述

以下泛型定义在Scala中有什么不同:

  class Foo [T<:List [_] ] 

  class Bar [T< ;: List [Any]] 

我的直觉告诉我他们大致相同,但后者更明确。我发现前者编译的情况,但后者没有,但不能把我的手指放在确切的区别。



谢谢!



编辑:

我可以再投入混合吗?

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


解决方案

好的,我想我应该对此表示赞同,而不是仅仅发表评论。对不起,如果你想让TLDR跳到最后,这将会很长。



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

  class Foo [T<:List [_]] 



  class Foo [T< T< ;:列表[Z] forSome {type Z}] 

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

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



<

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

请注意,很容易弄错它(正如我之前所做的那样):文章的作者Randall Shulz的答案引用了他自己的错误(请参阅评论),并在稍后修复它。这篇文章的主要问题是,在所示的例子中,使用existentials应该可以让我们免于打字问题,但事实并非如此。去检查代码,并尝试编译 compileAndRun(helloWorldVM(Test)) compileAndRun(intVM(42))。是的,不编译。简单地在 A 中编写 compileAndRun 泛型将会使代码编译,而且会更简单。
简而言之,这可能不是学习有关存在的最佳文章以及它们的优点(作者本人在评论中承认文章需要整理)。

所以我宁愿推荐阅读这篇文章: 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
}

这个类是通用的(注意也是不变的),但是我们可以看到 hello 确实没有使用类型参数(不像 getName ),所以如果我得到一个 Greets 的实例,我应该可以随时调用它, T 是。如果我想定义一个需要> Greets 实例并调用它的 hello 方法的方法,我可以试试这个:

  def sayHi1(g:Greets [T]){g.hello()} //不会编译

果然,这不会编译,因为 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)//不会编译
$ b $ p最后一行不能编译,因为 Greets 是不变的,所以 Greets [String] 和 Greets [Symbol] 不能被视为 Greets [Any] ,即使 String Symbol 均扩展任何



好吧,让我们用一个简化符号 _ 来试试一个存在体:

  val greetsList2:List [Greets [_]] = List(greets1,greets2)//编译好,是

这个编译很好,你可以像预期的那样做:

  greetsSet foreach(_.hello)

现在,记住我们在第一个类型检查问题的原因地点是b因为 Greets 是不变的。如果它变成一个协变类( class Greets [+ T] ),那么所有东西都可以在盒子中运行,我们永远不会需要存在。






因此,总结一下,存在是有用的处理泛型不变类,但如果泛型类不需要自己出现将参数类型转换为另一个泛型类,可能是你不需要存在,只需要在你的方法中添加一个类型参数就可以工作了现在回来(终于,我关于

  class Foo [T<:List [_]] 

因为 List 是协变的,所以这是为了所有意图和目的同样只是说:

  class Foo [T<:List [Any]] 


>但是,如果用 Se替换 List

  class Foo [T<:Set [_]] 

Set 是不变的,因此我们处于相同的状态情况与我例子中的 Greets 类一样。因此,上面与

  class Foo [T <:Set [Any]] 


What is the different between the following Generics definitions in 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]]

解决方案

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 TLDR skip to the end.

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 fo this post, thanks to Jesper Nordenberg for pointing it out) this not the same as:

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

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

The important point that you you should 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
}

This class is generic (note also that is is invariant), but we can see that hello really don't make 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

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

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

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

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

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

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)

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.


So to sum up, existentials are usefull 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[_]]

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.

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

class Foo[T <: Set[_]]

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天全站免登陆