en

Packages and Imports - Scala for the impatient

07 Apr 2019 | 4mins

This is my small contribution to Gympass’ Scala study group. Scala for the impatient, chapter 7: Packages and Imports.

Packages and Imports

Packages are used to reference specific classes, objects, traits, etc. Imports are used to access packages from other files and use definitions by its simple name.

In this chapter, the author presents how packages and import statements work in Scala. Key Points are:

Packages

To add items to a package, we can include them in package statements, such as:

    
      package com {
        package horstmann {
          package impatient {
            class Employee
            ...
          }
        }
      }
    

Then the class name Employee can be accessed anywhere as com.horstmann.impatient. Employee.

Unlike Classes or objects definitions, packages can be defined in multiple files.(Ex: Manager class on package com.horstmann.impatient.manager)

OBS: there is no enforced relationship between the source file and the package. employee.scala doesn’t have to be on com/horstmann/impatient directory.

we can contribute to more than one package in a single file.

Scope Rules

Everything defined inside the parent package can be used without the full package reference. Ex:

    
      package com {
        package horstmann {
          object Utils {
            def percentOf(value: Double, rate: Double) = value * rate / 100
            ...
          }
          package impatient {
            class Employee {
              ...
              def giveRaise(rate: scala.Double) {
                salary += Utils.percentOf(salary, rate)
              }
            }
          }
        }
      }
  

We can use Utils.percentOf instead of com com.horstmann.Utils.percentOf because it is used inside the parent scope.

If a package is defined with a conflicting name (Ex: collection vs scala.collection), one solution is to use absolute package names. Another approach is to use chained package clauses.

Chained Package Clauses

    
      package com {
        package horstmann {
          package collection {
          ...
          }
        }
      }
  

Instead of using the above example, we can use the following way, so com.horstmann.collection package would no longer be accessible as ‘collection’:

    
      package com.horstmann.collection {
      // Members of com and com.horstmann are not visible here
        }
      }
    

Top-of-File Notation

Instead of the nested notations presented until now, we can use a cleaned notation without braces:

    
      package com.horstmann.impatient
      package people
      class Person
      ...
    

The package definition will extend to the entire file and is equivalent to:

    
      package com.horstmann.impatient {
        package people {
          class Person
          ...
          // Until the end of the file
        }
      }
    

Package Objects

Packages can contain classes objects and traits, but not methods or variables. To address this issue we can use package objects.

Every package can have its package object(only 1 per package), and it should be defined inside the parent.

    
      package com.horstmann.impatient
        package object people {
          val defaultName = "John Q. Public"
        }
        package people {
          class Person {
            var name = defaultName // A constant from the package
        }
        ...
      }
    

Package Visibility

We can use qualifiers to explicit the visibility of a method.

    
      package com.horstmann.impatient.people
      class Person {
        private[people] def description = "A person with name " + name
        ...
      }
    

We can extend the visibility to an enclosing package:

    
      private[impatient] def description = "A person with name " + name
    

Imports

If we import packages, we can use shorter names instead of longer ones by bringing them into scope. (Ex: Color instead of java.awt.Color after importing it)

To import all members of a package use the wildcard ._ (Ex: import java.awt._)

Imports Can Be Anywhere

Imports can be anywhere inside a Scala file, not only on top. The scope extends until the end of the enclosing block.

It is helpful to reduce potential namespace conflicts. It also makes our code more clear and organized. Ex:

    
      package foo

      // available to all classes defined below
      import java.io.File
      import java.io.PrintWriter

      class Foo {
          // only available inside this class
          import javax.swing.JFrame
          // ...
      }

      class Bar {
          // only available inside this class
          import scala.util.Random
          // ...
      }
    

However, import statements are read in the order of the file, so the code below won’t compile:

    
      // this doesn't work because the import is after the attempted reference
      class ImportTests {
          def printRandom {
              val r = new Random   // fails
          }
      }

      import scala.util.Random
    

Renaming and Hiding Members

To import a few members from a package, we can use a selector:

    
      import java.awt.{Color, Font}
    

On selectors, we can rename members using =>

    
      import java.util.{HashMap => JavaHashMap}
    

Renaming is important when you use classes with the same name from different packages, or to abbreviate a name. (Ex: javal.util.Date & java.sql.Date)

The alternative to renaming is to use fully qualified names (package.Class) inside the code, but it’s verbose.

Implicit Imports

Every Scala program starts with:

    
      import java.lang._ // String, Object, Exception
      import scala._     // Int, Nothing, Function
      import Predef._    // println, ???
    

OBS: some scala package members override the java correspondents. (Ex: StringBuilder)

Hey, thanks for reading :D ! Any thoughts on this topic? Comment below! ;)