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]]
Given the following definition − val t = (4,3,2,1) To access elements of a tuple t, you can use method t. _1 to access the first element, t. _2 to access the second, and so on.
case _ => does not check for the type, so it would match anything (similar to default in Java). case _ : ByteType matches only an instance of ByteType . It is the same like case x : ByteType , just without binding the casted matched object to a name x .
To use a generic class, put the type in the square brackets in place of A . The instance stack can only take Ints. However, if the type argument had subtypes, those could be passed in: Scala 2.
The classes that takes a type just like a parameter are known to be Generic Classes in Scala. This classes takes a type like a parameter inside the square brackets i.e, [ ].
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.
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:
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 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
}
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
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 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[_]]
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]]
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 List
is unknown to class Foo
rather than your second form, which says specifically that the List
's element type is Any
.
Check out this brief explanatory blog article on Existential Types in Scala (EDIT: this link is now dead, a snapshot is available at archive.org)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With