Pre-SIP: Parametric Top


I view extends Top as a better version of extends AnyVal — there’s a tradeoff in expressivity, but I’d never use AnyVal. extends Top or opaque types don’t let you change equals, but if you want the features of either, you need to switch to typeclass-based equality (not multiversal equality).

Type itself doesn’t, but you still box in generic contexts unless you specialize:

type T = Int // defined type alias T
def foo[U](u: U) = u //foo: [U](u: U)U
foo[T](1) //res0: T = 1 //boxing HERE

A better discussion of what boxes when is in the README for (@jvican that might help for revisions of the SIP) — I doubt you can improve on the generated code much.

I think that might be a valid and important usability concern about the concrete syntax. Maybe this should be written type Foo(a: Int) extends Top, newtype Foo(a: Int), or yet something else. In all cases, Foo would extend Top but not Any/AnyVal/AnyRef.

As long as it’s not forgotten about, this should be discussed separately so that we get the right semantics before bikeshedding the details (Pre-SIP: Unboxed wrapper types). And I’m not implying at all concrete syntax doesn’t matter, it’s just harder to discuss.

EDIT: @jvican is the plan to sketch out this proposal before choosing? It can be fine if Martin leaves details as exercises to the reader, but we still need to see the worked out version. I understand parts of the result doesn’t look very different from your earlier document on “newtype classes” ( extends Newtype), but that’s not clear. Also, details on Top are spread in a discussion over the Dotty repo that didn’t quite reach a conclusion.

  • Parametric Top: They are different types but the backend will optimize them as the same underlying type after type erasure.
  • Opacity type: They are the same type but the typer did not know.


I’m not so sure … I think the complications of adding a new Top type
to Scala are at least as great as changing the semantics of Any and
adding AnyObj to match Dotty.

In that case I’d much prefer to keep the original proposal and keep Any as the true top type!

It’d be interesting to make the change and try it with a few
representative projects and see how much breaks.

If you get around to do this, I’d be really interested in the results!


That’s a good way to put it.


I’m probably not making myself clear — simply stated, the JVM does not allow checkcast on primitives, so they have to be boxed once. Or do I miss something? That boxing is not due to newtypes but is still relevant.

Ah yes, of course. The question is only whether newtypes do any additional boxing.


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

  1. drop all methods except asInstanceOf on Any,

  2. Introduce a new subclass class AnyObj which has the methods that were dropped on Any.


    (The text above used Top for Any and Any for AnyObj but it does not matter much either way what names are used.

  3. 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.


Just my 2 cents, but I think if significant portions of the standard library would have to break parametricity because it’s not feasible to introduce (among others) Equals and Shows typeclasses then parametric Any/Top is also not worth it. Almost everything a basic program could do would break parametricity anyway.


This problem can be resolved if we force that a top type always equal to its underlying type. We don’t have to drop runtime abilities on java.lang.Object, like eq, getClass, equals and hashCode methods. We can simply make those methods final and always forward those methods to their underlying implementations.

case class MyValueClass(s: String) extends Top

MyValueClass("foo") == "foo" // true
MyValueClass("foo".intern()) eq "foo".intern() // true
MyValueClass("foo").getClass // java.lang.String

Very simple. Nothing breaks.


The original design of Parametric Top is problematic because lack of runtime equality ability, however the syntax of Parametric Top looks more concise than Opacity Types.

Why not keep both the equality behavior from Opacity Types and the syntax from Parametric Top altogether?


Surely a type class Option would be a good thing. AnyRef could then use null as its None instance, and avoid wrapping altogether. As i understand it currently an Option Int is double wrapped, with a type class it could be reduced to a single wrapping.

One thing I’ve often wondered about it whether you could use -2 power 31 as the None value for an Option[Int]. But maybe that would create too many problems. The name could be changed to Opt at the same time. Very commonly used names should be short.

Switching to Type classes would certainly be painful. But if we were designing Scala from scratch would anyone now seriously argue against Type classes for parametrised types taking a parameter not inheriting from AnyRef? So maybe the Type class path is in the long run inevitable and its just a question of how long we take to accept the inevitable and how long we string out the pain of denying it.


You mean this allocation-less Option???

It works without any SIP.
Works in for comprehensions etc.
Only boxes primitives once.


Or maybe @sjrd’s scala-unbox-option:


@sjrd’s version is nicer, as it can represent Some(None). As it turns out, there’s no need to use null to represent absent values; any singleton object will do –– one for None, one for Some(None), one for Some(Some(None)), etc. That allows Option not to violate monad laws.


It’s not only “nicer”; it’s the only one that is even remotely viable. Breaking nesting is not bad because it breaks monad laws. It’s very very bad because it breaks Map[A, Option[B]].get(), and any other similar thing where you have Option[T] and you happen to instantiate T to another option.

My implementation still breaks toString and type tests, though (which is unavoidable for an unboxed option).