Irregularities in the Scala class hierarchy

Scala imposes a very elegant class hierarchy over a system such as Java, crawling out of Any from above, into AnyRef and AnyVal to cover Java objects and primitives, respectively, and then finally converges, collapses reference types to Null and all types to Nothing. As far as I understand, nothing is a subtype of everything; Zero is a subtype of all subtypes of AnyRef / java.lang.Object. [cm. http://www.scala-lang.org/node/128 ]

However, there seem to be several irregularities, in several places where this does not work, to just think of all Scala types as elements of a hierarchy of seamless type. I find this fierce and I want to understand places where I might be surprised.

So far, I know a few violations:

1) Although Null is a subtype of AnyRef, a call to null.isInstanceOf [AnyRef] (or other subtypes of AnyRef) returns false. I suspect this has been chosen to match the behavior of the Java instanceof operator.

2) Everything is covariant to Nothing, regardless of deviation annotations. If I have a method that returns a type T that is not marked covariant, I can override this method to return a type of Nothing. [NOTE: this statement is erroneous, see answers and comments below!]

3) I cannot apply isInstanceOf to type AnyVal [See Why can't AnyVal be used in checking isInstanceOf? and How to check the value on AnyVal?

4) You cannot ask a question: something isInstanceOf [Null], which is a completely sequential task (although this is not particularly necessary, since "myVar == null" will give the same answer)

Are there other examples of bumps or special cases in the Scala type hierarchy? I feel that they deserve to be studied and understood in order to avoid unpleasant surprises.

+4
source share
1 answer

1) "A string is a subtype of AnyRef".isInstanceOf[AnyRef] returns true . This is also true for sub- AnyRef other than Null . The only irregularity there, as you said, corresponds to Java.

2) If B is a subtype of A , that is, B <: A , you can always override the method:

 def foo: A = ... 

in

 override def foo: B = ... 

This is called return type refinement and is always allowed. Since Nothing is a subtype of any other type ( Nothing <: A for all A ), you can always specify your return type to Nothing (for example, by throwing an exception in the method body). This is a fairly regular property. The covariance of the return type is not directly related to the annotations of variations in the type parameters .

3) The remaining questions are well covered.

4) This is because the Null type does not exist in the Java runtime. I think if you wanted to emulate this, you could create your own instanceOf method - you first need to check if the argument is Null , otherwise do the usual isInstanceOf check.

There are other violations, yes. See For example: If Int cannot be null, what does the value null.asInstanceOf [Int] mean?

Arrays are another example where you can pay uniformity to shared arrays using boxing / unpacking or instanceOf checks at runtime. new Array[Any] converted to an array of objects - storing an integer in the array will lead to its boxing. Whenever you use Array[T] , where T does not have an upper bound, the array will have a pattern mapped to the correct runtime array type each time the element is indexed.

To better understand how you might be surprised, it is useful to think about how these constructions are converted to JVMs, where there is the concept of primitive and reference types, boxing / unpacking, and different classes of classes.

+7
source

Source: https://habr.com/ru/post/1439029/


All Articles