Tour of Scala

Top Level Definitions in Packages

Language

Often, it is convenient to have definitions accessible across an entire package, and not need to invent a name for a wrapper object to contain them.

Scala 2 provides package objects as a convenient container shared across an entire package.

Package objects can contain arbitrary definitions, not just variable and method definitions. For instance, they are frequently used to hold package-wide type aliases and implicit conversions. Package objects can even inherit Scala classes and traits.

In a future version of Scala 3, package objects will be removed in favor of top level definitions.

By convention, the source code for a package object is usually put in a source file named package.scala.

Each package is allowed to have one package object. Any definitions placed in a package object are considered members of the package itself.

In Scala 3, any kind of definition can be declared at the top level of a package. For example, classes, enums, methods and variables.

Any definitions placed at the top level of a package are considered members of the package itself.

In Scala 2 top-level method, type and variable definitions had to be wrapped in a package object. These are still usable in Scala 3 for backwards compatibility. You can see how they work by switching tabs.

See example below. Assume first a class Fruit and three Fruit objects in a package gardening.fruits:

// in file gardening/fruits/Fruit.scala
package gardening.fruits

case class Fruit(name: String, color: String)
object Apple extends Fruit("Apple", "green")
object Plum extends Fruit("Plum", "blue")
object Banana extends Fruit("Banana", "yellow")

Now assume you want to place a variable planted and a method showFruit directly into package gardening.fruits. Here’s how this is done:

// in file gardening/fruits/package.scala
package gardening
package object fruits {
  val planted = List(Apple, Plum, Banana)
  def showFruit(fruit: Fruit): Unit = {
    println(s"${fruit.name}s are ${fruit.color}")
  }
}
// in file gardening/fruits/package.scala
package gardening.fruits

val planted = List(Apple, Plum, Banana)
def showFruit(fruit: Fruit): Unit =
  println(s"${fruit.name}s are ${fruit.color}")

As an example of how to use this, the following program imports planted and showFruit in exactly the same way it imports class Fruit, using a wildcard import on package gardening.fruits:

// in file PrintPlanted.scala
import gardening.fruits._

object PrintPlanted {
  def main(args: Array[String]): Unit = {
    for (fruit <- planted) {
      showFruit(fruit)
    }
  }
}
// in file printPlanted.scala
import gardening.fruits.*

@main def printPlanted(): Unit =
  for fruit <- planted do
    showFruit(fruit)

Aggregating Several Definitions at the Package Level

Often, your project may have several reusable definitions defined in various modules, that you wish to aggregate at the top level of a package.

For example, some helper methods in the trait FruitHelpers and some term/type aliases in trait FruitAliases. Here is how you can put all their definitions at the level of the fruit package:

Package objects are like other objects, which means you can use inheritance for building them. So here we mix in the helper traits as parents of the package object.

package gardening

// `fruits` instead inherits its members from its parents.
package object fruits extends FruitAliases with FruitHelpers

In Scala 3, it is preferred to use export to compose members from several objects into a single scope. Here we define private objects that mix in the helper traits, then export their members at the top level:

package gardening.fruits

private object FruitAliases extends FruitAliases
private object FruitHelpers extends FruitHelpers

export FruitHelpers.*, FruitAliases.*

Contributors to this page: