So, we did some brainstorming on what a conversion to parametric top would entail. The basics look easy but there’s one show-stopper, and that’s parametric classes and in particular parametric case classes - shoutout to @OlivierBlanvillain for having brought up the problem.
The basics would be
-
drop all methods except asInstanceOf
on Any
,
-
Introduce a new subclass class AnyObj
which has the methods that were dropped on Any
.
Any
|
AnyObj
|
Object
(The text above used Top
for Any
and Any
for AnyObj
but it does not matter much either way what names are used.
-
Introduce a deprecated conversion from Any
to AnyObj
@deprecated Any2AnyObj(x: Any): AnyObj = x.asInstanceOf[AnyObj]
That’s mostly it. There are some refinements on pattern matching but nothing too important. It would mean that most code should port cleanly, at the price of deprecation messages where methods were used on Any
.
However, we have a problem: How should we express a class such as Some[T]
?. Should that be Some[T <: AnyObj]
? That would be awkward because it would mean that we cannot return an optional result over instances of unconstrained type parameters. So the only sane alternative is to keep it as Some[T <: Any]
. But then, how should be define equals
and hashCode
on such a class?
If we keep the original definition, it would look like this:
def equals(that: Any) = that match {
case that: Some => Any2AnyObj(this).x.==(that.x)
case _ => false
}
Note that it’s impossible to give a proper definition of equals
without the deprecated Any2AnyObj
conversion (or the cast in which it is implemented). So, we can’t define equals
and hashCode
for Some
in a non-deprecated way!
Now, we could try to do without equals
and hashCode
but I am afraid this would invalidate a large part of all Scala code out there without an obvious remedy. The upheaval of going to an equality type class would be enormous; among other things it would shut the door to any meaningful interop with Java.
We could still possibly live with the situation and accept that case class equals and hashCode violate the parametricity constraint. For instance, we could accept that one can now observe whether two values x
and y
of an unbounded type parameter are the same by writing
Some(x) == Some(y)
Not nice, but maybe that’s the only reasonable compromise. However, it looks to me then that the idea of saying that value classes extending Top
never have to box is untenable. The previous approach was “these are classes, but we never need to create instances of these classes because nobody will be able to observe the difference between a class instance and its underlying value”. Nobody means: Only code that breaks the parametricity constraint using the deprecated conversion or a cast will observe the difference. But now that means that using any case class is enough to observe the difference because every case class has to use the unparametric conversion in its implementation of equality.
I think it’s still worthwhile to explore the idea of parametric top (either as the default upper bound or as a bound that needs to be given explicitly). But for unboxed value classes I think going with the opaque type proposal is the safer bet.