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?
Scala 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 in Scala is a mechanism to encapsulate a group of classes, sub packages, traits and package objects. It basically provides namespace to put our code in a different files and directories. Packages is a easy way to maintain our code which prevents naming conflicts of members of different packages.
In Scala, an object is a named instance with members such as fields and methods. An object and a class that have the same name and which are defined in the same source file are known as companions. Companions has special access control properties, which is covered under Scala/Access modifiers.
With Scala you can: Import packages, classes, objects, traits, and methods. Place import statements anywhere. Hide and rename members when you import them.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With