Okay, I decided that I need to accept this instead of just posting comments. Sorry, this will be a long time if you want TL; DR missed to the end.
As Randall Schultz said, here is a contraction from the existential type. Namely,
class Foo[T <: List[_]]
this abbreviation for
class Foo[T <: List[Z] forSome { type Z }]
Note that contrary to what Randall Schultz's answer mentions (full disclosure: I also made a mistake in an earlier version of this post, thanks to Jesper Nordenberg for pointing this out), this is not the same as:
class Foo[T <: List[Z]] forSome { type Z }
and this is not the same as:
class Foo[T <: List[Z forSome { type Z }]]
Beware, it’s easy to make a mistake (as my previous deception shows): the author of the article referenced by Randall Schulz’s answer himself made a mistake (see Comments) and corrected it later. My main problem in this article is that in the example shown, the use of existentials should save us from the typing problem, but it is not. Check the code and try compiling compileAndRun(helloWorldVM("Test")) or compileAndRun(intVM(42)) . Yes, it does not compile. Simply creating compileAndRun generic in A will compile the code, and it will be much simpler. In short, this is probably not the best article to find out about existentials and why they are good (the author himself admits in a comment that the article "needs to be cleaned").
Therefore, I would recommend reading this article: http://www.artima.com/scalazine/articles/scalas_type_system.html , in particular, the sections Existential Types and Dispersion in Java and Scala.
The important point you should get from this article is that existentials are useful (besides being able to deal with common Java classes) when working 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 universal (note that it is invariant), but we can see that hello does not really use a type parameter (as opposed to getName ), so if I get an instance of Greets I should always call this, no matter what T If I want to define a method that accepts an instance of Greets and just calls its hello method, I could try this:
def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
Of course, this does not compile since T appears out of nowhere.
Ok, then let's make the method general:
def sayHi2[T]( g: Greets[T] ) { g.hello() } sayHi2( Greets("John")) sayHi2( Greets('Jack))
Great, it works. We could also use existentials here:
def sayHi3( g: Greets[_] ) { g.hello() } sayHi3( Greets("John")) sayHi3( Greets('Jack))
Also works. Thus, in general, there is no real benefit from using an existential (like in sayHi3 ) type parameter (like in sayHi2 ).
However, this changes if Greets appears as a type parameter for another generic class. For example, we want to save several instances of Greets (with different T ) in the list. Let's try this:
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 an invariant, therefore Greets[String] and Greets[Symbol] cannot be considered as Greets[Any] although String and Symbol both extend Any .
Ok, let's try with existence using the abbreviated notation _ :
val greetsList2: List[Greets[_]] = List( greets1, greets2 )
This compiles fine and you can do as expected:
greetsSet foreach (_.hello)
Now, remember that the reason we had a type checking problem was because Greets invariant. If it were turned into a covariant class ( class Greets[+T] ), then everything would work out of the box, and we would never need existential components.
So, to summarize, existentials are useful for working with universal invariant classes, but if a universal class does not have to appear itself as a type parameter for another universal class, there is a possibility that you do not need existentials and just adding a type parameter to your method will work
Now go back (finally, I know!) To your specific question regarding
class Foo[T <: List[_]]
Since List is covariant, for all intents and purposes it is the same as simply saying:
class Foo[T <: List[Any]]
So in this case, using any designation is really just a matter of style.
However, if you replace List with Set , everything will change:
class Foo[T <: Set[_]]
Set invariant, and so we are in the same situation as with the Greets class from my example. So the above is really very different from
class Foo[T <: Set[Any]]