Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Set[_] vs Set[Any]

Tags:

scala

I have the following line of code:

case set: Set[Any] => setFormat[Any].write(set)

However, the compiler issues a warning:

non-variable type argument Any in type pattern scala.collection.Set[Any] is unchecked since it is eliminated by erasure [warn]

Fair enough.

So I change my line to this:

case set: Set[_] => setFormat[Any].write(set)

Now I get an error:

[error] found : scala.collection.Set[_]

[error] required: scala.collection.Set[Any]

Q1. What is the difference between these two?

Then I change my code to the following:

case set: Set[_] => setFormat[Any].write(set.map(s => s))

Now it is happy with no errors or warnings.

Q2. Why does this work??

like image 441
PolyglotPiglet Avatar asked Mar 04 '15 23:03

PolyglotPiglet


1 Answers

Q1: A Set[Any] is a Set whose elements type is Any. A Set[_] is a Set whose elements type is unknown. Maybe it's a Set[Int], maybe a Set[String], maybe a Set[Any]. Contrary to most (immutable) collections, Set is not covariant (declaration is trait Set[A], not trait Set[+A]). So a Set[String] is not a Set[Any], and more generally, you cannot guarantee that a set whose elements type you do not know (i.e. Set[_]) is a Set[Any].

Q2: It works because whatever the (unknown) type A of the elements of set, the identity function s => s can be considered to a a function A => Any. (variance is Function1[-T1, +R]. Then, the resulting set.map(s => s) can be typed as Set[Any], as required.

Remark: Difficult to be sure without the definition of setFormat and write, but do you really need to be explicit with the [Any] type argument in setFormat[Any]? One may pass an existential to a generic function, i.e

val x: X[_] = ....
def f[A](xa: X[A]) = ...
f(x) // allowed

but being explicit at the call site (e.g f[Any](x)) will not be allowed, as we do not know whether X is an X[Any].

Note: about Set not being covariant: This is unfortunate, as one feels very much that a set of Cats is a set of Animals too. Here is one reason for that (there might be others).

Set has a method def contains(a: A): Boolean and this signature prevents covariance. Other collections have a def contains[A1 >: A](a: A): Boolean, which allows covariance, but which is effectively equivalent to def contains(a: Any): Boolean.

It works, because the implementation is based on method equals, available everywhere (comes with the JVM) and which takes an argument of type Any. It is quite likely that calling with a value of a type unrelated to the list content is a mistake, and that a more constrained signature would be better, but it is a small price to pay for covariance.

But this relaxed signature for contains constraints the implementation to be based on equals (and possibly hashCode too). It would not work for an implementation based on Ordering, which would not accept an untyped argument. Forbidding such (very common) set implementations may be seen as too high a price for covariance.

like image 99
Didier Dupont Avatar answered Sep 25 '22 01:09

Didier Dupont