Search code examples
javascalatype-systems

How does Scala's type system compare to Java's?


In one of the Stack Overflow answers it is quoted that:

Scala is a fully object oriented language, more so than Java, with one of the most advanced type systems available on non-research languages.

In what ways is Scala's type system more advanced than Java's?


Solution

  • Scala's type system can do pretty much everything Java's can (with some warts removed, like covariant arrays). In addition, it has the following features:

    Variance annotations

    An abstract class C that is generic on T can be made a subtype of C[U] where U is a subtype or a supertype of T.

    class C[+T] // C[T] <: C[U] iff T <: U
    class D[-T] // C[T] <: C[U] iff U <: T
    

    This is incredibly useful when passing around immutable data structures parameterised on the type of values they contain. For example, List[String] is a subtype of List[Any].

    Java uses wildcards for this, thereby moving taking care of this to the user of the API rather than the definer. This is suboptimal in many cases.

    Path-dependent types

    In Java, a value of non-static inner class type stores a pointer to the object of the containing class type. In Scala this is also the case, except it also works this way in the type system. For example:

    class P {
      class C { }
    }
    
    val x = new P
    val y = new P
    var z = new x.C
    z = new y.C // type error; x.C and y.C are distinct types
    

    Java lacks this feature; x and y both have type P.C.

    Higher-kinded types

    A type cannot only be parameterised on another type but also on a type constructor:

    trait Functor[F[_]] {
      def map[T, U](function: T => U)(functor: F[T]): F[U]
    }
    

    This is most useful in type classes like Functor and Monad.

    Java lacks this feature.

    Structural types

    A type is a subtype of a structural type if it contains all members of the structural type.

    type S = { def x: String; def y: Int }
    
    class A { def x = "a"; def y = 1 }
    class B { def y = 1 }
    

    Here, A is a subtype of S because it defines both def x: String and def y: Int which are required by S. B is not a subtype of S because B does not define def y: Int. Note that in many cases, reflection is used when accessing members of values of which the static type is a structural type, and therefore structural typing is often discouraged.

    They can be used to simulate type lambdas, though, without runtime cost.

    Java lacks this feature, and it's arguably not very useful anyway.

    Abstract types

    Like methods, types can be abstract:

    trait T {
      type G
      def f: G
    }
    
    class C extends T {
      override type G = Int
      override def f = 42
    }
    

    Java lacks this feature.

    Singleton types

    The singleton type of x contains x and only x. This can be useful in particular when one wants to guarantee that a method returns this:

    trait T {
      def x: this.type
    }
    
    class C extends T {
      def x = this
    }
    
    val x: T = new C
    x.x // has type x.type (which is a subtype of C), not type T
    

    In Java this isn't possible; you either have to parameterise T and rely on CRTP, or have x return T instead of C.

    Bottom type

    Scala has the bottom type Nothing that is a subtype of all types and contains no values. This is the type of throw expressions and the return type of functions that never return (i.e. always throw an exception or enter an infinite loop).

    Java lacks this feature and uses void instead. Scala has no void but makes the distinction between functions that return no value and non-returning functions explicit.