Usually, the programs we write are pretty large and contain many files with classes, variables, functions, and other types, so we often need to include external or standard libraries. Entities in our program may have similar names but refer to different things and have different functionalities. So, in order not to write huge logical object names, we use namespaces that serve to organize code in the form of logical groups and avoid name conflicts. Like many popular languages, Scala can import code. Let's see how and what can be imported into Scala!
Packages
The simplest namespace is the package. Packages are created by declaring one or more package names at the top of a Scala file. We write the package keyword and then, separated by dots, the full name of the package. For example:
package users
class User(val login: String)
One convention is to name the package the same as the Scala file's directory. However, Scala is agnostic to file layout. The directory structure of the project for package users might look like this:
- ExampleProject
- build.sbt
- project
- src
- main
- scala
- users
User.scala
Main.scala
- test
- scala
- users
UserTest.scalaImports
How can we use elements from other namespaces? For this, we have the import keyword. import clauses are for accessing members (classes, traits, functions, and so on) in other packages. It's not required to access members of the same package.
We write import and then specify all the way to the namespace that we want to use. Let's import the class User, for example:
package fitness
import users.User
object FitnessClub:
def swim(user: User) = println(s"$user, come to swimming pool and compete")
val gym = "Work hard!"
val firstUser = User("Andrew")
The interesting thing is that not just packages but objects and other variables are also namespaces. We can even import names from it:
import fitness.FitnessClub.swim // import function
import fitness.FitnessClub.firstUser // import class instance
import fitness.FitnessClub.gym // import variable
We can also expand dynamic entities, for example, by having a class instance, we may want to import a field from it. This is applicable when we have to use something from the same entity many times:
import fitness.FitnessClub.firstUser.name
Often we need to reuse code that has already been written to avoid doing extra work, so we can import elements of the standard library and various other libraries:
import scala.collection
import scala.concurrent
import scala.math
This is how you can use entities from different libraries.
Import selector clause
What about convenient imports? There are several tricks that will make it more convenient for you to manage imported elements.
- Wildcard. The
*character is used as the wild character in Scala. In the example below, everything is imported from ouruserspackage.
import users.* // import everything from the users package
- Multiple import. Do we really have to write a line for each of the declared names in order to import it? No! If we want to import several names from the same namespace at once, we can put a dot in front of the names and list all the names we are importing in curly brackets.
import users.{User, UserPreferences}
- Renaming. To avoid namespace collisions, you may have to rename the members while importing into the scope.
import users.{UserPreferences as UPrefs}
- Hiding. Scala provides an option to hide one or more classes while importing other members from the same package.
import users.{UserPreferences as _, *}
In the example above, UserPreferences is hidden and everything else from the users package is imported into the scope.
- Limiting the scope. We can write imports not only at the beginning of the file, but also in any other place, for example, inside a function. This allows us to control the scope of the imported members.
object VipZone:
def giveAccess = {
import fitness.FitnessClub.firstUser.name
println(s"give access to $name")
}Conclusion
In this topic, you got acquainted with namespaces and their use in Scala and figured out what a package is. You learned that import statements can be anywhere: at the start of a class, within a class or object, or within a method or block. Members can be renamed or hidden while importing. Packages, classes, or objects can be imported into the current scope. Let's apply these functionalities and features in practice to write structured, easy-to-read, and extensible code!