Scala Tutorial

Basics

Control Statements

OOP Concepts

Parameterized - Type

Exceptions

Scala Annotation

Methods

String

Scala Packages

Scala Trait

Collections

Scala Options

Miscellaneous Topics

Scala Type Hierarchy

Scala has a rich type system, and understanding the type hierarchy is crucial for effective programming in the language. At the top of the hierarchy, there are two universal types: Any and Nothing. Let's explore the Scala type hierarchy in detail:

  1. Any:

    • This is the supertype of all types in Scala. Every class in Scala inherits from the Any class, either directly or indirectly.
    • It defines methods like equals, hashCode, and toString that are universal to all Scala objects.
  2. AnyVal:

    • This is the parent type for all built-in value types in Scala. These are types that are often represented as primitive values at runtime in platforms like the JVM to achieve better performance.
    • Subtypes of AnyVal include: Double, Float, Long, Int, Char, Short, Byte, Unit, and Boolean.
    • For example, Int in Scala is a subtype of AnyVal and is usually represented as a primitive int in the JVM at runtime.
  3. AnyRef:

    • This represents the superclass of all reference types in Scala. On the JVM, AnyRef corresponds to java.lang.Object.
    • Every non-value type in Scala is a subtype of AnyRef. This includes all user-defined classes, collections, and more.
    • For instance, a List[Int] or a custom class like class Person would be a subtype of AnyRef.
  4. Nothing:

    • This is a subtype of every other type in the Scala type hierarchy.
    • It's unique because there are no values of type Nothing. It's used to indicate abnormal termination, such as throwing an exception.
    • Because Nothing is a subtype of all types, it can be used as a type parameter to indicate certain kinds of behavior. For example, the Nil object, which represents an empty list, is of type List[Nothing].
  5. Null:

    • It's a subtype of all AnyRef types (i.e., all reference types) but not of value types.
    • The type Null has a single value: null. This is the default value for all reference types and corresponds to Java's null reference.
  6. Specialized Types:

    • Scala has a mechanism to generate specialized versions of generic classes for specific primitive types to avoid boxing and unboxing operations and improve performance. This is done using the @specialized annotation.
  7. Abstract Types and Path-Dependent Types:

    • These are more advanced features of the type system, allowing for abstract type members inside traits and classes and types that depend on a value (or "path").
  8. Type Bounds:

    • Scala's generics also allow for type bounds, which can be upper (<:) or lower (>:) bounds, restricting the types that can be used as type arguments.

In summary, understanding the Scala type hierarchy, especially the distinctions between AnyVal, AnyRef, Nothing, and Null, can help when navigating the rich type system of the language. The ability to use and understand advanced features like path-dependent types, abstract types, and type bounds further enhances the power and flexibility of Scala's type system.

Trait hierarchy in Scala

Traits in Scala are similar to interfaces in other languages, providing a way to declare and share fields and methods among classes. They form a hierarchy when extended.

trait Printable {
  def print(): Unit
}

class MyClass extends Printable {
  def print(): Unit = println("Printing...")
}

Object hierarchy in Scala

Object hierarchy in Scala is rooted at Any, and all non-primitive types inherit from it. There are two main branches: AnyVal for value types and AnyRef for reference types.

class MyClass

val instance: AnyRef = new MyClass
val value: AnyVal = 42

Scala AnyVal vs AnyRef in type hierarchy

AnyVal represents value types with no identity, and AnyRef represents reference types with identity.

val intValue: AnyVal = 42
val refValue: AnyRef = "Hello"

// Use pattern matching to distinguish
intValue match {
  case x: AnyVal => println("It's a value type")
  case _ => println("It's not a value type")
}

Covariance and contravariance in Scala types

Covariance allows subtyping relationships to be preserved, while contravariance reverses the relationships.

class Container[+A](val value: A)

val intContainer: Container[Int] = new Container(42)
val anyContainer: Container[Any] = intContainer

Scala type bounds and variance

Type bounds specify constraints on type parameters, and variance defines how subtyping relationships are inherited.

class Box[A](val value: A)

def printValue(box: Box[_ <: AnyVal]): Unit = {
  println(box.value)
}

val intBox = new Box(42)
val stringBox = new Box("Hello")

printValue(intBox)
// printValue(stringBox) // Error, as it requires AnyVal

Special types in Scala hierarchy

Scala introduces special types like Nothing and Null at the bottom of the type hierarchy.

def error(message: String): Nothing = throw new RuntimeException(message)

val nullValue: Null = null

Implicits in Scala type hierarchy

Implicits are powerful in Scala and are used for type conversions, parameter injection, etc.

implicit def intToString(value: Int): String = value.toString

def printString(str: String): Unit = println(str)

val intValue: Int = 42
printString(intValue) // Implicit conversion from Int to String