Add `Matchable` trait

As far as I can tell the clone method, which is part of Object has not been discussed in this thread, nor its associated interface Cloneable. I assume for the short term it would be included in Any like most of the others. What do you think we should do with it in the longer term? Manage it with a type class?

1 Like

clone is a protected method in Object/AnyRef, not Any, so itā€™s not really relevant here.

As far as Iā€™m aware, clone is sort of ignored by the Java world as well (artima - Josh Bloch on Design), so the answer might be ā€œdo nothingā€

By that logic all AnyVals are also AnyRefs because they can be boxed. Iā€™d argue itā€™s not an implementation detail because itā€™s observable. Instead of an implementation detail Iā€™d say boxing is an implicit conversion, just one in Java-land instead of Scala user-land with our implicit keyword. So yeah IMO Matchable is clearer than AnyClass.

1 Like

Donā€™t want to get in a big offtopic discussion about this, but how would you as a programmer observe the difference if in a future scala version Int is always represented by a java.lang.Integer instance instead of by the more efficient int where possible? I donā€™t consider profiling the heap or the runtime performance an answer, unless you think that the scalac optimizer and the JIT should be part of the language specification. You could inspect the Class instance, but the compiler could still defer a.getClass to ScalaRunTime.anyValClass just like it does now when a is statically known to be Int.

I donā€™t think AnyVal should be matchable. In fact, I donā€™t think AnyRef should be either, as that exposes what should be implementation details related to boxing:

@ List(1: Any).map(_.isInstanceOf[AnyRef])
res0: List[Boolean] = List(true)

@ List(1: Any).map(_.isInstanceOf[AnyVal])
cmd1.sc:1: type AnyVal cannot be used in a type pattern or isInstanceOf test
val res1 = List(1:Any).map(_.isInstanceOf[AnyVal])
                                         ^

The first line above should not compile any more than the second, because its semantics is just as broken.

This is fine, on the other hand, and Int can still be made matchable:

@ List(1: Any).map(_.isInstanceOf[Int])
res0: List[Boolean] = List(true)

Matchable has nothing to do with the target type of the match. It has to with the source type, i.e., the type of the scrutinee.

(That said, we could independently warn off x.isInstanceOf[AnyRef] as being equivalent to x != null.)


Since Iā€™m here: I am warming up to the idea of this proposal. I think Matchable is a good name.

2 Likes

Ah, I thought Iā€™d read somewhere that it also prevented using the type in a type test, which would also make sense.

Iā€™d like to have a way of opting out of traits being usable as sources and being usable as targets of type tests.

Not that itā€™s hugely important, but would it make sense to change the definition of isInstanceOf to something like:

def isInstanceOf[T <: Matchable]: Boolean

?

1 Like

How would that be any better than what it is now, i.e., <: Any?

It would rule out something like this:

object opaques:
  opaque type Logarithm = Double

def weirdTest(d: Double) = d.isInstanceOf[Logarithm]

If thatā€™s desirable.

That is already ruled out.

Right. All abstract and opaque types are ruled out?

Yes.

1 Like

Maybe Iā€™m taking you to literally (accidental habit of mine) but IIUC here are a few examples:

  1. Array[Int] and Array[Integer] are mutually incompatible
  2. Patmatā€™ing to AnyRef fails with Int and succeeds for Integer
  3. Having an AnyRef upper-bound on a method (i.e. def x[A <: AnyRef])

(2) and (3) might sound esoteric but I actually use both approaches quite often in code, usually around performance-related stuff where using a ref and doing ref comparisons is fast and effective, where as a function with the same purpose but for Ints, will get a different implementation with different logic.

If the bounds of isInstanceOf were adjusted to isInstanceOf[A <: Matchable] then you could remove whatever code is in the compiler that currently prevents isInstanceOf[OpaqueType] and get it for free. Not to mention it would be clearer and more accurate to anyone looking at the method.

No, the checks performed on the target type of an instance test are not directly related to that, but the above is a consequence. The checks are that the target type has a stable public erasure, basically. That is more general than just checking non matchable stuff. So we would still have to keep that code.

Also, you still donā€™t want to be able to type test for an opaque type alias with an upper bound that is Matchable. So we would still have to keep that code.

The rules on the left and in the right have nothing to do with each other. On the left, you want not to break parametricity. On the right, you want a stable, public erasure to be able to perform the type test in the first place. These are orthogonal.

3 Likes

Ah ok cool. I thought it was a nice idea but thanks for clarifying.

@LPTK has a point about non-Matchable traits though, in particular universal traits. It would make sense to require it. But maybe it could be added later in that case.

New issue with Matchable that hasnā€™t been discussed so far:

1 Like