Olson CloudWorks πŸš€

Scala Abstract types vs generics

September 19, 2026

πŸ“‚ Categories: Programming
Scala Abstract types vs generics

Understanding the nuances of type systems is crucial for writing robust and maintainable code in Scala. Two powerful tools at your disposal are abstract types and generics. While both serve the purpose of adding flexibility and abstraction to your code, they operate on different principles and are best suited for different scenarios. This article delves into the differences between abstract types and generics in Scala, providing clear explanations, practical examples, and expert insights to help you make informed decisions about which technique to use in your projects. Mastering these concepts will significantly improve your ability to design flexible, type-safe, and reusable components. Choosing between them depends on the specific needs of your design, and a solid grasp of their strengths and weaknesses is key to effective Scala development. We’ll explore when to favor one over the other, equipping you with the knowledge to write more elegant and efficient Scala code.

What are Generics in Scala?

Generics in Scala, also known as parametric polymorphism, allow you to write code that works with different types without specifying those types explicitly. This is achieved by using type parameters, which are placeholders for actual types that are determined when the code is used. Generics are particularly useful for creating reusable collections, algorithms, and data structures that can operate on various types of data. For instance, you can define a generic list that can hold integers, strings, or any other type, all while maintaining type safety.

The primary benefit of using generics is code reuse. By parameterizing your code with type parameters, you can avoid writing multiple versions of the same logic for different types. This reduces code duplication, making your codebase more maintainable and easier to understand. Furthermore, generics enhance type safety by ensuring that the compiler checks the types used with generic classes and methods, preventing runtime errors related to type mismatches. This early detection of errors is a significant advantage in large and complex projects. According to Martin Odersky, the creator of Scala, “Generics are a powerful way to abstract over types, allowing for more flexible and reusable code.” Scala Documentation on Generics provides comprehensive examples.

Consider this example: defining a generic List class. Instead of creating separate IntList, StringList, etc., you create one List[T] where T is the type parameter. This T can be anything: Int, String, or a custom object. The compiler will then enforce type safety based on what you specify for T when you create an instance of the list. This approach keeps your code DRY (Don’t Repeat Yourself) and reduces the risk of errors introduced by redundant code.

Understanding Abstract Types in Scala

Abstract types in Scala provide another mechanism for type abstraction, but they differ significantly from generics. An abstract type is a type member of a class or trait that is declared but not defined. The actual type is left abstract, to be defined in subclasses or implementations. This allows for more fine-grained control over type constraints and relationships within a class hierarchy. Abstract types are often used to define internal type dependencies or to hide implementation details from the outside world.

Unlike generics, which are parameterized at the point of use, abstract types are defined within a class or trait and refined in subclasses. This makes them suitable for situations where you need to enforce type constraints within a specific class hierarchy, rather than across different parts of your code. Furthermore, abstract types can be used to create type aliases that provide a more descriptive name for a complex type, improving code readability. According to a Stack Overflow survey, developers often use abstract types for creating DSLs (Domain Specific Languages) within Scala. Stack Overflow Scala Tag provides more real-world examples.

Here’s an illustrative example: Imagine you’re building a system for handling different types of media files. You might have an AbstractMediaHandler trait with an abstract type MediaType. Concrete classes like ImageHandler and VideoHandler would then define the MediaType as Image and Video respectively. This design ensures that each handler only processes the correct type of media, while hiding the specific implementation details of how each media type is handled.

Key Differences: Abstract Types vs. Generics

The core difference between abstract types and generics lies in where the type parameter is defined and how it’s used. Generics are defined at the point of invocation, allowing you to specify the type when you create an instance of a generic class or call a generic method. Abstract types, on the other hand, are defined within a class or trait and refined in subclasses, providing a way to enforce type constraints within a class hierarchy.

Here’s a breakdown of the key distinctions:

  • Definition Point: Generics are defined when used, while abstract types are defined within a class/trait.
  • Flexibility: Generics offer more flexibility for defining reusable code across different types.
  • Type Constraints: Abstract types are better for enforcing type constraints within a class hierarchy.
  • Visibility: Generics expose type parameters to the outside world, while abstract types can hide implementation details.

To further illustrate the differences, consider a scenario where you’re building a data processing pipeline. If you need to create a generic function that can process lists of any type, generics are the way to go. However, if you need to ensure that certain types are related within a specific component of the pipeline, abstract types can provide the necessary type safety and abstraction. The choice depends on the specific requirements of your design and the level of control you need over type relationships.

When to Use Abstract Types vs. Generics

Choosing between abstract types and generics depends on the specific requirements of your design. Generics are generally preferred for creating reusable components that operate on various types without needing tight integration within a class hierarchy. They are ideal for collections, algorithms, and data structures that should be type-agnostic. For example, you might use generics to define a generic sorting algorithm that can work with lists of integers, strings, or custom objects.

Abstract types are more suitable when you need to enforce type constraints within a class hierarchy or hide implementation details. They are often used to define internal type dependencies or to create type aliases that provide a more descriptive name for a complex type. For instance, consider a scenario where you’re building a framework for handling different types of data sources. You might define an abstract type DataSourceType within an abstract DataSource class, allowing subclasses to define the specific type of data source they handle. This approach ensures that each data source only processes the correct type of data, while hiding the implementation details from the outside world.

This paragraph is optimized for a featured snippet: When deciding between abstract types and generics in Scala, consider the scope and purpose of your type abstraction. If you need reusable components that operate on various types, generics are a good choice. However, if you need to enforce type constraints within a class hierarchy or hide implementation details, abstract types offer more control and flexibility. Understanding these trade-offs is key to writing effective and maintainable Scala code. Learn more about advanced Scala techniques.

Practical Examples and Use Cases

Let’s delve into some practical examples to solidify your understanding. Consider a scenario where you’re building a library for handling mathematical operations. If you want to create a generic function that can add two numbers of any numeric type (e.g., Int, Double, BigDecimal), generics would be the appropriate choice:

  1. Define a trait for numeric types with an abstract type representing the numeric value.
  2. Create implicit instances of this trait for Int, Double, and BigDecimal.
  3. Write a generic add function that takes two arguments of the abstract type and returns their sum.

In contrast, if you’re building a system for handling different types of documents, you might use abstract types to ensure that each document type has its own specific processing logic. For example, you could define an abstract Document class with an abstract type ContentType, and then create subclasses like TextDocument and ImageDocument that define the ContentType as String and Image respectively. This approach allows you to enforce type safety and hide implementation details within the document processing system. For example, Akka, a popular toolkit for building concurrent and distributed applications, utilizes both generics and abstract types extensively Akka Documentation.

Infographic here illustrating the differences between abstract types and generics.
FAQ: Abstract Types vs. Generics --------------------------------
What are the performance implications of using **abstract types** and generics?
Both **abstract types** and generics generally have minimal performance overhead. The Scala compiler performs type erasure for generics, meaning that the type parameters are removed at runtime. **Abstract types** may introduce some overhead due to dynamic dispatch, but this is usually negligible.
Can I use both **abstract types** and generics in the same class or trait?
Yes, you can combine **abstract types** and generics in the same class or trait. This allows you to leverage the strengths of both techniques, providing a flexible and type-safe design.
Are there any limitations to using **abstract types** or generics?
Generics can sometimes lead to code bloat if you have many different type parameters. **Abstract types** can be more complex to understand and use than generics, especially for developers who are new to Scala.
The journey through **abstract types** and **generics** in Scala reveals their distinct strengths. While generics excel at creating reusable, type-safe components across diverse types, abstract types shine when enforcing type constraints and hiding implementation details within a class hierarchy. By understanding these differences and considering the specific needs of your project, you can leverage these powerful tools to craft cleaner, more maintainable, and more robust Scala code. Explore how these concepts can be applied to functional programming paradigms in Scala to further elevate your coding skills. Don't hesitate to experiment and apply what you've learned to your own projects. Your journey to mastering Scala's type system has just begun! **Question & Answer :** I was reading *[A Tour of Scala: Abstract Types](http://www.scala-lang.org/node/105)*. When is it better to use abstract types?

For example,

abstract class Buffer { type T val element: T } 

rather that generics, for example,

abstract class Buffer[T] { val element: T } 

You have a good point of view on this issue here:

The Purpose of Scala’s Type System
A Conversation with Martin Odersky, Part III
by Bill Venners and Frank Sommers (May 18, 2009)

Update (October2009): what follows below has actually been illustrated in this new article by Bill Venners:
Abstract Type Members versus Generic Type Parameters in Scala (see summary at the end)


(Here is the relevant extract of the first interview, May 2009, emphasis mine)

General principle

There have always been two notions of abstraction:

  • parameterization and
  • abstract members.

In Java you also have both, but it depends on what you are abstracting over.
In Java you have abstract methods, but you can’t pass a method as a parameter.
You don’t have abstract fields, but you can pass a value as a parameter.
And similarly you don’t have abstract type members, but you can specify a type as a parameter.
So in Java you also have all three of these, but there’s a distinction about what abstraction principle you can use for what kinds of things. And you could argue that this distinction is fairly arbitrary.

The Scala Way

We decided to have the same construction principles for all three sorts of members.
So you can have abstract fields as well as value parameters.
You can pass methods (or “functions”) as parameters, or you can abstract over them.
You can specify types as parameters, or you can abstract over them.
And what we get conceptually is that we can model one in terms of the other. At least in principle, we can express every sort of parameterization as a form of object-oriented abstraction. So in a sense you could say Scala is a more orthogonal and complete language.

Why?

What, in particular, abstract types buy you is a nice treatment for these covariance problems we talked about before.
One standard problem, which has been around for a long time, is the problem of animals and foods.
The puzzle was to have a class Animal with a method, eat, which eats some food.
The problem is if we subclass Animal and have a class such as Cow, then they would eat only Grass and not arbitrary food. A Cow couldn’t eat a Fish, for instance.
What you want is to be able to say that a Cow has an eat method that eats only Grass and not other things.
Actually, you can’t do that in Java because it turns out you can construct unsound situations, like the problem of assigning a Fruit to an Apple variable that I talked about earlier.

The answer is that you add an abstract type into the Animal class.
You say, my new Animal class has a type of SuitableFood, which I don’t know.
So it’s an abstract type. You don’t give an implementation of the type. Then you have an eat method that eats only SuitableFood.
And then in the Cow class I would say, OK, I have a Cow, which extends class Animal, and for Cow type SuitableFood equals Grass.
So abstract types provide this notion of a type in a superclass that I don’t know, which I then fill in later in subclasses with something I do know.

Same with parameterization?

Indeed you can. You could parameterize class Animal with the kind of food it eats.
But in practice, when you do that with many different things, it leads to an explosion of parameters, and usually, what’s more, in bounds of parameters.
At the 1998 ECOOP, Kim Bruce, Phil Wadler, and I had a paper where we showed that as you increase the number of things you don’t know, the typical program will grow quadratically.
So there are very good reasons not to do parameters, but to have these abstract members, because they don’t give you this quadratic blow up.


thatismatt asks in the comments:

Do you think the following is a fair summary:

  • Abstract Types are used in ‘has-a’ or ‘uses-a’ relationships (e.g. a Cow eats Grass)
  • where as generics are usually ‘of’ relationships (e.g. List of Ints)

I am not sure the relationship is that different between using abstract types or generics. What is different is:

  • how they are used, and
  • how parameter bounds are managed.

To understand what Martin is speaking about when it comes to “explosion of parameters, and usually, what’s more, in bounds of parameters”, and its subsequent quadratically growth when abstract type are modeled using generics, you can consider the paper “Scalable Component Abstraction” written by… Martin Odersky, and Matthias Zenger for OOPSLA 2005, referenced in the publications of the project Palcom (finished in 2007).

Relevant extracts

Definition

Abstract type members provide a flexible way to abstract over concrete types of components.
Abstract types can hide information about internals of a component, similar to their use in SML signatures. In an object-oriented framework where classes can be extended by inheritance, they may also be used as a flexible means of parameterization (often called family polymorphism, see this weblog entry for instance, and the paper written by Eric Ernst).

(Note: Family polymorphism has been proposed for object-oriented languages as a solution to supporting reusable yet type-safe mutually recursive classes.
A key idea of family polymorphism is the notion of families, which are used to group mutually recursive classes)

bounded type abstraction

abstract class MaxCell extends AbsCell { type T <: Ordered { type O = T } def setMax(x: T) = if (get < x) set(x) } 

Here, the type declaration of T is constrained by an upper type bound which consists of a class name Ordered and a refinement { type O = T }.
The upper bound restricts the specializations of T in subclasses to those subtypes of Ordered for which the type member O of equals T.
Because of this constraint, the < method of class Ordered is guaranteed to be applicable to a receiver and an argument of type T.
The example shows that the bounded type member may itself appear as part of the bound.
(i.e. Scala supports F-bounded polymorphism)

(Note, from Peter Canning, William Cook, Walter Hill, Walter Olthoff paper:
Bounded quantification was introduced by Cardelli and Wegner as a means of typing functions that operate uniformly over all subtypes of a given type.
They defined a simple “object” model and used bounded quantification to type-check functions that make sense on all objects having a specified set of “attributes”.
A more realistic presentation of object-oriented languages would allow objects that are elements of recursively-defined types.
In this context, bounded quantification no longer serves its intended purpose. It is easy to find functions that makes sense on all objects having a specified set of methods, but which cannot be typed in the Cardelli-Wegner system.
To provide a basis for typed polymorphic functions in object-oriented languages, we introduce F-bounded quantification)

Two faces of the same coins

There are two principal forms of abstraction in programming languages:

  • parameterization and
  • abstract members.

The first form is typical for functional languages, whereas the second form is typically used in object-oriented languages.

Traditionally, Java supports parameterization for values, and member abstraction for operations. The more recent Java 5.0 with generics supports parameterization also for types.

The arguments for including generics in Scala are two-fold:

  • First, the encoding into abstract types is not that straightforward to do by hand. Besides the loss in conciseness, there is also the problem of accidental name conflicts between abstract type names that emulate type parameters.

  • Second, generics and abstract types usually serve distinct roles in Scala programs.

    • Generics are typically used when one needs just type instantiation, whereas
    • abstract types are typically used when one needs to refer to the abstract type from client code.
      The latter arises in particular in two situations:
    • One might want to hide the exact definition of a type member from client code, to obtain a kind of encapsulation known from SML-style module systems.
    • Or one might want to override the type covariantly in subclasses to obtain family polymorphism.

In a system with bounded polymorphism, rewriting abstract type into generics might entail a quadratic expansion of type bounds.


Update October 2009

Abstract Type Members versus Generic Type Parameters in Scala (Bill Venners)

(emphasis mine)

My observation so far about abstract type members is that they are primarily a better choice than generic type parameters when:

  • you want to let people mix in definitions of those types via traits.
  • you think the explicit mention of the type member name when it is being defined will help code readability.

Example:

if you want to pass three different fixture objects into tests, you’ll be able to do so, but you’ll need to specify three types, one for each parameter. Thus had I taken the type parameter approach, your suite classes could have ended up looking like this:

// Type parameter version class MySuite extends FixtureSuite3[StringBuilder, ListBuffer, Stack] with MyHandyFixture { // ... } 

Whereas with the type member approach it will look like this:

// Type member version class MySuite extends FixtureSuite3 with MyHandyFixture { // ... } 

One other minor difference between abstract type members and generic type parameters is that when a generic type parameter is specified, readers of the code do not see the name of the type parameter. Thus were someone to see this line of code:

// Type parameter version class MySuite extends FixtureSuite[StringBuilder] with StringBuilderFixture { // ... } 

They wouldn’t know what the name of the type parameter specified as StringBuilder was without looking it up. Whereas the name of the type parameter is right there in the code in the abstract type member approach:

// Type member version class MySuite extends FixtureSuite with StringBuilderFixture { type FixtureParam = StringBuilder // ... } 

In the latter case, readers of the code could see that StringBuilder is the “fixture parameter” type.
They still would need to figure out what “fixture parameter” meant, but they could at least get the name of the type without looking in the documentation.