We should know better than to question Our Lord Variance.
If I can make contains final I can mostly avoid it, and I can catch ClassCastException, but the first requires "deprecated non-final" and with the second you can't help but grit your teeth.
scala> class Bippy extends Seq[Int] { override def contains(x: Int) = false ; def iterator = Nil.iterator ; def apply(idx: Int) = ??? ; def length = 0 }defined class Bippy
That contains is overriding the "root laws of Variance" IIRC. i.e. contains takes "Any" because A is not in a covariant location...
I'm not quite sure what's happening there though....
Looks like contract violation, the @unckeckedVariance needs to be required in the override, and probably even disallow concrete type (Int). But I'm not sure, it is very hard to think about half-solutions :-)
We should know better than to question Our Lord Variance.If I can make contains final I can mostly avoid it, and I can catch ClassCastException, but the first requires "deprecated non-final" and with the second you can't help but grit your teeth.scala> class Bippy extends Seq[Int] { override def contains(x: Int) = false ; def iterator = Nil.iterator ; def apply(idx: Int) = ??? ; def length = 0 }defined class Bippy
scala> var x: Seq[Any] = new Bippyx: Seq[Any] = ()scala> x contains "blue"java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integerat scala.runtime.BoxesRunTime.unboxToInt(Unknown Source)at Bippy.contains(<console>:7)at .<init>(<console>:10)
On Tue, Oct 9, 2012 at 11:13 AM, Jan Vanek <j3v...@gmail.com> wrote:Looks like contract violation, the @unckeckedVariance needs to be required in the override, and probably even disallow concrete type (Int). But I'm not sure, it is very hard to think about half-solutions :-)I'm pretty sure it's a necessary feature of annotations in general that they are not inherited. My sense is that only finalizing contains will be sufficient. And it would be sufficient, and I wouldn't have any objection to doing it since I want to finalize most things, but there's the usual bugbear of world-as-it-is.Most plausible route I can think of is a newly named, final method. I already tried to get clever with this once before, in NumericRange, with unsatisfying still-undealt-with results:scala> 1L to 10L contains 3res7: Boolean = falsescala> (1L to 10L).toList contains 3res8: Boolean = trueWhy would that be, you might ask? Here is why:- Variance: Even though NumericRange is invariant, it inheritscontains from covariant Seq and thereby must suffer its signature.Inability to abstract across variance strikes again.
true
if this range has an element that is is equal (wrt ==
) to elem
, false
otherwise.- Since a NumericRange is implemented in terms of some unknown T, the onlyways to implement contains are to iterate over every element calling== or to transform an "Any" into a T. Iteration will give the right answer:scala> 1L to 10L exists (_ == 3)res0: Boolean = truebut when one pictures doing this to see if 1L to Int.MaxValue.toLongcontains Int.MaxValue / 2, we appreciate the many orders ofmagnitude speed reduction we potentially pay for correctness. Whichtakes us to:
- Leaky primitive abstraction: casting a numeric primitive to anothernumeric primitive always succeeds. Casting between boxed types willalways fail. Calling a method taking Any boxes the parameter. Andthis is why we can't find 3 in a range containing 3L: the cast fromjava.lang.Integer to java.lang.Long fails.
There are a number of possible ways out, none very appealing. Myplan right now is to type match the contains argument and widen toLong or Double if it's a primitive, then call the (not yet existing)fromLong or fromDouble method on Numeric which (will) have theeach-Numeric-specific knowledge of how to create a T.
Well that's the thing, it doesn't take Any after I change it not to take Any
The == is the one that bugs me. But sure, contains would take A instead. Maybe the mutable collections being invariant have something to tell Mr. Immutable Seq.
Marginally related:
class X
class CA[+T]
class CB[T <: X] extends CA[T]
val cb = new CB[X]
val ca1: CA[X] = cb
val ca2: CA[Any] = ca1 // can we make this fail, please
I don't want to convert to use-site variance camp.
On Tue, Oct 9, 2012 at 4:32 PM, Jan Vanek <j3v...@gmail.com> wrote:
Marginally related:
class X
class CA[+T]
class CB[T <: X] extends CA[T]
val cb = new CB[X]
val ca1: CA[X] = cb
val ca2: CA[Any] = ca1 // can we make this fail, please
I don't want to convert to use-site variance camp.
Why do you want that to fail? It meets all the requirements. If an instance of CB cannot handle its type parameter being widened beyond X when it is viewed as a CA,
then it violates the Liskov Substitution Principle. So allowing it looks perfectly okay to me. (ca1 is the dangerous step anyway.)
--Rex
--Rex