Understanding the type system in scala


#1
abstract class Foo
  object Test extends Foo{
    val foo  = new Foo{
      val x = 3
    }
  }
println(s"printing foo.x = ${Test.foo.x}")
  println(s"printing the type of Test.foo = ${Test.foo.getClass}")

The second print statements prints
printing the type of Test.foo = class TryOut$Test$$anon$1
Isn’t the foo’s type Foo?


#2

No, foo's type is foo.Type, which is an anonymous type (you’ve specialized/subclasses the Foo type via an inlined class template rather than giving it a name). The JVM class which implements this anonymous type is named as you see in the output, which shows the nesting of enclosing types as well as the anonymous-class-ness of the type.


#3

If it is an anonymous type, in what way is it related to Foo (the abstract class)? I mean it won’t compile if I remove the abstract class Foo line.


#4

First off, let’s get the terminology straight: A class is not the same as a type. A type exists only at compile time. You can’t “get the type” at runtime (so you can’t print it as a variable), because it doesn’t exist at runtime. For a very good explainer, see https://typelevel.org/blog/2017/02/13/more-types-than-classes.html

Types are in part implemented through classes.

getClass gets the name of the implementing class. This is an implementation detail of how scalac implements scalas typesystem on the JVM.

The type of foo is the structural type Foo { val x: Int }, which is a subtype of Foo

The runtime class is apparently TryOut$Test$$anon$1. This is an implementation detail that isn’t useful knowledge in many cases.


#5

foo is not a completely anonymous type. It’s an anonymous subclass of Foo, which makes it an anonymous subtype of class Foo.


#6

foo is not a completely anonymous type

I’m not sure that “completely anonymous” vs. “anonymous subclass” is a meaningful distinction in Scala. For that matter, i’m only using “anonymous type” as an English shorthand for “not a named type”, where “named type” is a term used in the scala spec. However, i think that’s in line with the intuitive and widespread, non-scala-specific use of the phrase (see: https://en.wikipedia.org/wiki/Anonymous_type#Example_(Scala) ).

Technically foo's type is foo.type, which is an “unnamed structural compound (refinement) type”. https://www.scala-lang.org/files/archive/spec/2.12/03-types.html#compound-types

It is structural because its refinement (val x) does not override a superclass’s definition of the same member.


#7

First, what @martijnhoekstra wrote is correct. Tho

@virus_dave: foo.type is a singleton type, as visible in its syntax, https://www.scala-lang.org/files/archive/spec/2.12/03-types.html#singleton-types. The compound/refinement type is Foo { val x: Int }, which is a supertype of foo.type. The spec is pretty specific on the syntax of those two:

A compound type T1 withwith Tn { R }
A singleton type is of the form p.p. type

Moreover, in this case, foo.type only contains foo and null, and is a strict subtype of Foo { val x: Int }, so you cannot confuse the two types even informally.

Now, asking about the type of foo is a somewhat ill-posed question, since multiple types are possible and since foo’s declaration and occurrences don’t have the same set of types, and some answers are more appropriate for one or the other.
For the declaration, the compiler will infer a type, most likely Foo { val x: Int } as @martijnhoekstra says; to know, the only way is to parse scalac -Xprint:typer, as type inference is not specified. Inferring foo.type as the type of the declaration would not be very helpful.

Each expression is given a type by type inference, which depends on the expression and the expected type (following colored local type inference; you can google the paper).
So, the most specific type possible for occurrences of a val (any val) is its singleton type, but the immediate supertype is (typically) the declaration type.

Finally, the spec gives a first approximation of the expected compiler behavior, which is not necessarily easy to parse.
To check if an expression e can be given a type T, try adding e : T and compiling. However, adding a type ascription changes the expected type used to infer e’s type; to avoid that, you can instead write final val x = e; x:T. Using final affects type inference of x’s type.
And to check the subtype relation, summon witnesses of <:< via implicitly.