Set variance again

28 views
Skip to first unread message

rklaehn

unread,
Dec 1, 2011, 2:29:34 PM12/1/11
to scala-user
Hi all,

I would really like to treat scala.collection.immutable.Set[T] as
covariant. It is possible to do this using an implicit conversion, but
I am not sure if this will cause trouble at some point (especially the
asInstanceOf). It seems to work just fine in the REPL though:

scala> val intSet = Set(1,2,3)
intSet: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> val s:Set[Any]=intSet
<console>:8: error: type mismatch;
found : scala.collection.immutable.Set[Int]
required: Set[Any]
Note: Int <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS
3.2.10)
val s:Set[Any]=intSet
^

scala> implicit def setIsCovariant[T,U <: T](s:Set[U]):Set[T] =
s.asInstanceOf[Set[T]]
setIsCovariant: [T, U <: T](s: Set[U])Set[T]

scala> val s:Set[Any]=intSet
s: Set[Any] = Set(1, 2, 3)

scala> val s:Set[String]=intSet
<console>:9: error: type mismatch;
found : scala.collection.immutable.Set[Int]
required: Set[String]
val s:Set[String]=intSet
^

Josh Suereth

unread,
Dec 1, 2011, 2:37:32 PM12/1/11
to rklaehn, scala-user
For immutable Set's you're probably ok.   The issue is about modification.   Set cannot be immutable because it accepts a T in it's "apply" method.  This *could* mean that it retains T's and if you drop down to Set[Any], it could accept any old Any, thereby breaking itself.

Imagine the method:

def add(x: T): Unit

This is why all mutable collections (sets included) are invariant.   The way Scala verifies invariance is more general than just "immutable vs. mutable".  So no, you cannot make your sets variant.

However, as you've seen, you can cast for variance (like in Java).   To be safe, if using mutable Sets, you really need to *copy* the collection....

Hope that helps!
- Josh

rklaehn

unread,
Dec 1, 2011, 2:46:55 PM12/1/11
to Josh Suereth, scala-user
Hi,

I am only concerned with immutable sets. As far as I know they are
only invariant so they can implement Function1[T,Boolean]. You can of
course use them as an Iterable[T] and get variance, but then you lose
the information that elements can not be contained twice etc.

What I was concerned about is that for example a Set[Int] might not
have a method contains(x:Any) but only a method contains(x:Int). But I
guess due to erasure the signature of the contains method when looking
at it using javap will always be contains(Object x), and even if
@specialized is used at some point there will just be an _additional_
Method contains(int x).

right?

Sonnenschein

unread,
Dec 2, 2011, 5:01:33 AM12/2/11
to scala-user
> For immutable Set's you're probably ok.
I guess the problem could be the same for immutable Sets when dealing
with those operations yielding a new instance such as +, couldn't it?

>   Set cannot be immutable because it accepts a T in it's "apply" method.

I suppose you meant 'covariant'.

>  This *could* mean that it retains T's and if you drop down to Set[Any], it
> could accept any old Any, thereby breaking itself.

I'd really like to see an example for the missbehavor we are fighting
all the time but unfortuanately I cannot produce any. Here is how I've
tried so far:

val s = collection.mutable.Set(1,2)
val sa = s.asInstanceOf[scala.collection.mutable.Set[Any]]

Now I can 'add' Strings or Ints etc. and ask for them by 'apply'
without any error.
sa add 1 // false
sa add 3 // true
sa add "A" // true
sa(4) // false
sa(3) // true
...

What am I missing? Do you have an idea how to demonstrate the devil?

Peter

Rüdiger Klaehn

unread,
Dec 2, 2011, 7:23:35 AM12/2/11
to Sonnenschein, scala-user
That's easy:

scala> val s=collection.mutable.Set(1,2,3)
s: scala.collection.mutable.Set[Int] = Set(2, 1, 3)

scala> val a=s.asInstanceOf[collection.mutable.Set[Any]]
a: scala.collection.mutable.Set[Any] = Set(2, 1, 3)

scala> a+="Bla"
res8: a.type = Set(Bla, 2, 1, 3)

scala> s
res9: scala.collection.mutable.Set[Int] = Set(Bla, 2, 1, 3)

s claims to be a Set of Int, but it contains a String. So you broke the type safety. Anybody who does something as simple as iterating over all keys will lead to a runtime exception:

scala> for(i<-s) println(i)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
        at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)
        at $anonfun$1.apply(<console>:12)
        at scala.collection.mutable.HashSet.foreach(HashSet.scala:72)
        at .<init>(<console>:12)
        at .<clinit>(<console>)
        at .<init>(<console>:11)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
        at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
        at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
        at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
        at java.lang.Thread.run(Unknown Source)

 

Reply all
Reply to author
Forward
0 new messages