96

What are package objects, not so much the concept but their usage?

I've tried to get an example working and the only form I got to work was as follows:

package object investigations {
    val PackageObjectVal = "A package object val"
}

package investigations {

    object PackageObjectTest {
        def main(args: Array[String]) {
            println("Referencing a package object val: " + PackageObjectVal)
        }
    }
}

Observations I've made so far are:

package object _root_ { ... }

is disallowed (which is reasonable),

package object x.y { ... }

is also disallowed.

It seems that a package object must be declared in the immediate parent package and, if written as above, the brace delimited package declaration form is required.

Are they in common use? If so, how?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Don Mackenzie
  • 7,953
  • 7
  • 31
  • 32
  • 7
    http://www.naildrivin5.com/scalatour/wiki_pages/PackageObjects – oluies Aug 04 '10 at 10:41
  • 1
    @Brent, this is a great resource, not just for the package object article. I've heard of the author but didn't realise he'd written this Scala tour, thanks. – Don Mackenzie Aug 04 '10 at 11:45
  • 1
    corrected link from @oluies - https://www.naildrivin5.com/scalatour/wiki_pages/PackageObjects/index.html – MIK Jan 23 '21 at 21:15

4 Answers4

134

Normally you would put your package object in a separate file called package.scala in the package that it corresponds to. You can also use the nested package syntax but that is quite unusual.

The main use case for package objects is when you need definitions in various places inside your package as well as outside the package when you use the API defined by the package. Here is an example:

// file: foo/bar/package.scala

package foo

package object bar {

  // package wide constants:
  def BarVersionString = "1.0"

  // or type aliases
  type StringMap[+T] = Map[String,T]

  // can be used to emulate a package wide import
  // especially useful when wrapping a Java API
  type DateTime = org.joda.time.DateTime

  type JList[T] = java.util.List[T]

  // Define implicits needed to effectively use your API:
  implicit def a2b(a: A): B = // ...

}

Now the definitions inside that package object are available inside the whole package foo.bar. Furthermore the definitions get imported when someone outside of that package imports foo.bar._.

This way you can prevent to require the API client to issue additional imports to use your library effectively - e.g. in scala-swing you need to write

import swing._
import Swing._

to have all the goodness like onEDT and implicit conversions from Tuple2 to Dimension.

Guillaume Massé
  • 8,004
  • 8
  • 44
  • 57
Moritz
  • 14,144
  • 2
  • 56
  • 55
  • 13
    Word of caution: method overloading doesn't work in package objects. – retronym Aug 04 '10 at 05:20
  • Beats me why it had been chosen that the package object should be defined one level up the package hierarchy. E.g. this means you need to pollute the virtual `org` or `com` top level package with your package object if you wish for it to belong to your own root package e.g. `org.foo`. I find that allowing the definition to be directly under the package it should be a part of - would have been slightly more proper language api interface. – matanster Oct 02 '15 at 16:22
  • Note that since at least Scala 2.10 and above overloading _does_ work in package objects. – Jasper-M Oct 02 '20 at 12:53
58

While Moritz's answer is spot on, one additional thing to note is that package objects are objects. Among other things, this means you can build them up from traits, using mix-in inheritance. Moritz's example could be written as

package object bar extends Versioning 
                          with JodaAliases 
                          with JavaAliases {

  // package wide constants:
  override val version = "1.0"

  // or type aliases
  type StringMap[+T] = Map[String,T]

  // Define implicits needed to effectively use your API:
  implicit def a2b(a: A): B = // ...

}

Here Versioning is an abstract trait, which says that the package object must have a "version" method, while JodaAliases and JavaAliases are concrete traits containing handy type aliases. All of these traits can be reused by many different package objects.

Dave Griffith
  • 20,435
  • 3
  • 55
  • 76
7

You could do worse than to go straight to the source. :)

https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/library/scala/package.scala

https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk/src/library/scala/collection/immutable/package.scala

Alex Cruise
  • 7,939
  • 1
  • 27
  • 40
  • @Alex Cruise, thanks, this seems to suggest that they need a separate compilation unit (which perhaps gets round the brace delimited package restriction). The problem is I want some solid user advice rather than my own conjecture about how to use them. – Don Mackenzie Aug 03 '10 at 21:33
7

The main use case for package objects is when you need definitions in various places inside your package as well as outside the package when you use the API defined by the package.

Not so with Scala 3, scheduled to be released mid-2020, based on Dotty, as in here:

Toplevel Definitions

All kinds of definitions can be written on the toplevel.
Package objects are no longer needed, will be phased out.

package p 

type Labelled[T] = (String, T) 
val a: Labelled[Int] = ("count", 1) 
def b = a._2 
def hello(name: String) = println(i"hello, $name)
Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thanks @VonC, I am really looking forward to Scala 3 for this and many other reasons. I have not made much use of package objects but I am sure I will use top level definitions. – Don Mackenzie Nov 13 '19 at 19:58