Scala - Abstract Types


Abstract types are used to define generic interfaces and abstract data types. So, you can create reusable and modular code structures. These types are defined within traits and abstract classes and are refined by concrete implementations.

Abstract types in Scala are types declared within traits (or abstract classes) without specifying their concrete implementations. Since there will be a high degree of abstraction and flexibility. So, you can define interfaces that can be concretely implemented later.

Defining Abstract Types

You can declare Abstract types within traits (or abstract classes). For example -

trait Container {
  type A
  def value: A
}

Here, A is an abstract type member of the Container trait. So concrete implementations of Container will need to specify what A is.

Refining Abstract Types

You must define the abstract type for concrete implementations -

class IntContainer extends Container {
  type A = Int
  def value: Int = 42
}

class StringContainer extends Container {
  type A = String
  def value: String = "Hello"
}

Here, IntContainer and StringContainer refine the abstract type A to Int and String, respectively. These provide concrete implementations for the value method.

Abstract Classes with Abstract Types

You can also use abstract types in abstract classes. For example -

abstract class Animal {
  type Sound
  def makeSound: Sound
}

class Dog extends Animal {
  type Sound = String
  def makeSound: String = "Woof!"
}

class Cat extends Animal {
  type Sound = String
  def makeSound: String = "Meow!"
}

Here, Animal defines an abstract type Sound. It is refined in the subclasses Dog and Cat to String.

Abstract Types vs. Type Parameters

Both abstract types and type parameters can have similar goals. But these are some different advantages -

  • Scope - You can define abstract types within the scope of a trait (or class) for better encapsulation.
  • Inheritance - You can refine abstract types in subclasses in complex inheritance hierarchies.
  • Type Aliases - You can use abstract types as type aliases for type definitions within a scope.

Example Comparison

Using type parameters -

trait Container[A] {
  def value: A
}

class IntContainer extends Container[Int] {
  def value: Int = 42
}

class StringContainer extends Container[String] {
  def value: String = "Hello"
}

Using abstract types -

trait Container {
  type A
  def value: A
}

class IntContainer extends Container {
  type A = Int
  def value: Int = 42
}

class StringContainer extends Container {
  type A = String
  def value: String = "Hello"
}

Both approaches achieve similar results. But abstract types can provide clearer and more concise code in certain contexts.

Type Bounds

You can have upper and lower bounds in Abstract types. It is similar to type parameters for more precise type constraints. For example -

trait BoundedContainer {
  type A <: Number
  def value: A
}

class IntContainer extends BoundedContainer {
  type A = Integer
  def value: Integer = 42
}

Here, A is bounded to be a subtype of Number for any concrete implementation of BoundedContainer. It must refine A to a subtype of Number.

Practical Example: Collections

You can use abstract types in the collection library of Scala. Following is the example which shows you how to simplified version of a generic collection -

trait MyCollection {
  type Elem
  def add(elem: Elem): MyCollection
  def head: Elem
}

class IntCollection extends MyCollection {
  type Elem = Int
  private var elements: List[Int] = Nil

  def add(elem: Int): MyCollection = {
    elements = elem :: elements
    this
  }

  def head: Int = elements.head
}

class StringCollection extends MyCollection {
  type Elem = String
  private var elements: List[String] = Nil

  def add(elem: String): MyCollection = {
    elements = elem :: elements
    this
  }

  def head: String = elements.head
}

Here, MyCollection is a trait with an abstract type Elem. The concrete implementations IntCollection and StringCollection define Elem as Int and String, respectively. These provide specific implementations for the methods.

Abstract Types Summary

  • Abstract types provide a high level of flexibility for concrete implementations to refine abstract type members.
  • You can define abstract types within traits and abstract classes. It can create generic and reusable interfaces.
  • You can have type bounds in abstract types for precise type constraints and type safety.
  • You can use abstract types in the collection library to provide generic and type-safe collections.