Scala 3 — Book

Packaging and Imports

Language

Scala uses packages to create namespaces that let you modularize programs and help prevent namespace collisions. Scala supports the package-naming style used by Java, and also the “curly brace” namespace notation used by languages like C++ and C#.

The Scala approach to importing members is also similar to Java, and more flexible. With Scala you can:

  • Import packages, classes, objects, traits, and methods
  • Place import statements anywhere
  • Hide and rename members when you import them

These features are demonstrated in the following examples.

Creating a package

Packages are created by declaring one or more package names at the top of a Scala file. For example, when your domain name is acme.com and you’re working in the model package of an application named myapp, your package declaration looks like this:

package com.acme.myapp.model

class Person ...

By convention, package names should be all lower case, and the formal naming convention is <top-level-domain>.<domain-name>.<project-name>.<module-name>.

Although it’s not required, package names typically follow directory structure names, so if you follow this convention, a Person class in this project will be found in a MyApp/src/main/scala/com/acme/myapp/model/Person.scala file.

Using multiple packages in the same file

The syntax shown above applies to the entire source file: all the definitions in the file Person.scala belong to package com.acme.myapp.model, according to the package clause at the beginning of the file.

Alternatively, it is possible to write package clauses that apply only to the definitions they contain:

package users {

  package administrators {  // the full name of this package is users.administrators
    class AdminUser        // the full name of this class users.administrators.AdminUser
  }
  package normalusers {     // the full name of this package is users.normalusers
    class NormalUser       // the full name of this class is users.normalusers.NormalUser
  }
}
package users:

  package administrators:  // the full name of this package is users.administrators
    class AdminUser        // the full name of this class is users.administrators.AdminUser

  package normalusers:     // the full name of this package is users.normalusers
    class NormalUser       // the full name of this class is users.normalusers.NormalUser

Note that the package names are followed by a colon, and that the definitions within a package are indented.

The advantages of this approach are that it allows for package nesting, and provides more obvious control of scope and encapsulation, especially within the same file.

Import statements, Part 1

Import statements are used to access entities in other packages. Import statements fall into two main categories:

  • Importing classes, traits, objects, functions, and methods
  • Importing given clauses

If you’re used to a language like Java, the first class of import statements is similar to what Java uses, with a slightly different syntax that allows for more flexibility. These examples demonstrate some of that flexibility:

import users._                            // import everything from the `users` package
import users.User                         // import only the `User` class
import users.{User, UserPreferences}      // import only two selected members
import users.{UserPreferences => UPrefs}  // rename a member as you import it
import users.*                            // import everything from the `users` package
import users.User                         // import only the `User` class
import users.{User, UserPreferences}      // import only two selected members
import users.{UserPreferences as UPrefs}  // rename a member as you import it

Those examples are meant to give you a taste of how the first class of import statements work. They’re explained more in the subsections that follow.

Import statements are also used to import given instances into scope. Those are discussed at the end of this chapter.

A note before moving on:

Import clauses are not required for accessing members of the same package.

Importing one or more members

In Scala you can import one member from a package like this:

import scala.concurrent.Future

and multiple members like this:

import scala.concurrent.Future
import scala.concurrent.Promise
import scala.concurrent.blocking

When importing multiple members, you can import them more concisely like this:

import scala.concurrent.{Future, Promise, blocking}

When you want to import everything from the scala.concurrent package, use this syntax:

import scala.concurrent._
import scala.concurrent.*

Renaming members on import

Sometimes it can help to rename entities when you import them to avoid name collisions. For instance, if you want to use the Scala List class and also the java.util.List class at the same time, you can rename the java.util.List class when you import it:

import java.util.{List => JavaList}
import java.util.{List as JavaList}

Now you use the name JavaList to refer to that class, and use List to refer to the Scala list class.

You can also rename multiple members at one time using this syntax:

import java.util.{Date => JDate, HashMap => JHashMap, _}
import java.util.{Date as JDate, HashMap as JHashMap, *}

That line of code says, “Rename the Date and HashMap classes as shown, and import everything else in the java.util package without renaming any other members.”

Hiding members on import

You can also hide members during the import process. This import statement hides the java.util.Random class, while importing everything else in the java.util package:

import java.util.{Random => _, _}
import java.util.{Random as _, *}

If you try to access the Random class it won’t work, but you can access all other members from that package:

val r = new Random   // won’t compile
new ArrayList        // works

Hiding multiple members

To hide multiple members during the import process, list them before using the final wildcard import:

import java.util.{List => _, Map => _, Set => _, _}
scala> import java.util.{List as _, Map as _, Set as _, *}

Once again those classes are hidden, but you can use all other classes in java.util:

scala> new ArrayList[String]
val res0: java.util.ArrayList[String] = []

Because those Java classes are hidden, you can also use the Scala List, Set, and Map classes without having a naming collision:

scala> val a = List(1, 2, 3)
val a: List[Int] = List(1, 2, 3)

scala> val b = Set(1, 2, 3)
val b: Set[Int] = Set(1, 2, 3)

scala> val c = Map(1 -> 1, 2 -> 2)
val c: Map[Int, Int] = Map(1 -> 1, 2 -> 2)

Use imports anywhere

In Scala, import statements can be anywhere. They can be used at the top of a source code file:

package foo

import scala.util.Random

class ClassA {
  def printRandom(): Unit = {
    val r = new Random   // use the imported class
    // more code here...
  }
}
package foo

import scala.util.Random

class ClassA:
  def printRandom(): Unit =
    val r = new Random   // use the imported class
    // more code here...

You can also use import statements closer to the point where they are needed, if you prefer:

package foo

class ClassA {
  import scala.util.Random   // inside ClassA
  def printRandom(): Unit = {
    val r = new Random
    // more code here...
  }
}

class ClassB {
  // the Random class is not visible here
  val r = new Random   // this code will not compile
}
package foo

class ClassA:
  import scala.util.Random   // inside ClassA
  def printRandom(): Unit =
    val r = new Random
    // more code here...

class ClassB:
  // the Random class is not visible here
  val r = new Random   // this code will not compile

“Static” imports

When you want to import members in a way similar to the Java “static import” approach—so you can refer to the member names directly, without having to prefix them with their class name—use the following approach.

Use this syntax to import all static members of the Java Math class:

import java.lang.Math._
import java.lang.Math.*

Now you can access static Math class methods like sin and cos without having to precede them with the class name:

import java.lang.Math._

val a = sin(0)    // 0.0
val b = cos(PI)   // -1.0
import java.lang.Math.*

val a = sin(0)    // 0.0
val b = cos(PI)   // -1.0

Packages imported by default

Two packages are implicitly imported into the scope of all of your source code files:

  • java.lang.*
  • scala.*

The members of the Scala object Predef are also imported by default.

If you ever wondered why you can use classes like List, Vector, Map, etc., without importing them, they’re available because of definitions in the Predef object.

Handling naming conflicts

In the rare event there’s a naming conflict and you need to import something from the root of the project, prefix the package name with _root_:

package accounts

import _root_.accounts._
package accounts

import _root_.accounts.*

Importing given instances

As you’ll see in the Contextual Abstractions chapter, in Scala 3 a special form of the import statement is used to import given instances. The basic form is shown in this example:

object A:
  class TC
  given tc: TC
  def f(using TC) = ???

object B:
  import A.*       // import all non-given members
  import A.given   // import the given instance

In this code, the import A.* clause of object B imports all members of A except the given instance tc. Conversely, the second import, import A.given, imports only that given instance. The two import clauses can also be merged into one:

object B:
  import A.{given, *}

In Scala 2, that style of import does not exist. Implicit definitions are always imported by the wildcard import.

Discussion

The wildcard selector * brings all definitions other than givens or extensions into scope, whereas a given selector brings all givens—including those resulting from extensions—into scope.

These rules have two main benefits:

  • It’s more clear where givens in scope are coming from. In particular, it’s not possible to hide imported givens in a long list of other wildcard imports.
  • It enables importing all givens without importing anything else. This is particularly important since givens can be anonymous, so the usual use of named imports is not practical.

By-type imports

Since givens can be anonymous, it’s not always practical to import them by their name, and wildcard imports are typically used instead. By-type imports provide a more specific alternative to wildcard imports, which makes it more clear what is imported:

import A.{given TC}

This imports any given in A that has a type which conforms to TC. Importing givens of several types T1,...,Tn is expressed by multiple given selectors:

import A.{given T1, ..., given Tn}

Importing all given instances of a parameterized type is expressed by wildcard arguments. For example, when you have this object:

object Instances:
  given intOrd: Ordering[Int]
  given listOrd[T: Ordering]: Ordering[List[T]]
  given ec: ExecutionContext = ...
  given im: Monoid[Int]

This import statement imports the intOrd, listOrd, and ec instances, but leaves out the im instance because it doesn’t fit any of the specified bounds:

import Instances.{given Ordering[?], given ExecutionContext}

By-type imports can be mixed with by-name imports. If both are present in an import clause, by-type imports come last. For instance, this import clause imports im, intOrd, and listOrd, but leaves out ec:

import Instances.{im, given Ordering[?]}

An example

As a concrete example, imagine that you have this MonthConversions object that contains two given definitions:

object MonthConversions:
  trait MonthConverter[A]:
    def convert(a: A): String

  given intMonthConverter: MonthConverter[Int] with
    def convert(i: Int): String =
      i match
        case 1 =>  "January"
        case 2 =>  "February"
        // more cases here ...

  given stringMonthConverter: MonthConverter[String] with
    def convert(s: String): String =
      s match
        case "jan" => "January"
        case "feb" => "February"
        // more cases here ...

To import those givens into the current scope, use these two import statements:

import MonthConversions.*
import MonthConversions.{given MonthConverter[?]}

Now you can create a method that uses those given instances:

def genericMonthConverter[A](a: A)(using monthConverter: MonthConverter[A]): String =
  monthConverter.convert(a)

Then you can use that method in your application:

@main def main =
  println(genericMonthConverter(1))       // January
  println(genericMonthConverter("jan"))   // January

As mentioned, one of the key design benefits of the “import given” syntax is to make it clear where givens in scope come from, and it’s clear in these import statements that the givens come from the MonthConversions object.

Contributors to this page: