Scala has pervasive support for nesting of classes/traits and objects within larger traits and objects. One long-standing pattern this allows that I find particularly expressive is the “traits as modules” style.
In this style
- traits define extensible modules that can contain data values, both abstract and concrete methods, and data types (ie traits/classes)
- Modules are instantiated by declaring an
object
that inherits one or more modules and ensures all abstract fields are implemented. - Modules can extend or depend on other modules by importing their object form, or inheriting from their trait form.
While the popularity among scala programmers of this pattern has waxed and waned over the years, the idea can be traced right back to Scala’s roots. It features in Odersky and Zenger’s 2005 paper “Scalable Module Abstractions” (actually there were a lot of IMO good ideas in this paper that perhaps deserve more attention & appreciation from the modern Scala audience, 18 years later).
However, Scala’s support for “traits as modules” is missing one important capability: it is not possible to natively declare what in Java is termed a static nested class.
Rather, Scala only supports what Java terms “inner classes”, which trail a reference to an instance of the containing trait.
So for example, if one defines a Person module containing a Person case class, every Person instance contains an extra field, a pointer to a PersonModule instance.
trait PersonModule:
case class Person(id: UUID, name: String)
There are probably times when this is desirable, although I don’t ever use it in practice. There are many times when at worst this is benign and harmless. But unfortunately, in computationally demanding scenarios where there are eg millions of instances of the class in memory, spending 8 bytes per-instance on a completely unused field is a problem.
As a workaround, one can define the class outside the module-trait and export it, which simulates a static nested class:
trait PersonModule:
export module_internal.{Person}
package module_internal
case class Person(id: UUID, name: String)
However, the workaround causes a number of difficulties. Because the class definitions and companion objects (like Person
above) must live outside the module, methods defined in them cannot refer to other symbols defined within the module without hitting circular dependency problems.
It would be much preferable to be able to express static nested classes natively in the Scala language. In my view, static nesting is typically the most useful form of nesting. It allows placing a datatype into a module namespace, without introducing any runtime cost.