Skip to content

Talk about top-level-definitions in the tour of Scala #2579

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 93 additions & 9 deletions _tour/package-objects.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
---
layout: tour
title: Package Objects
title: Top Level Definitions in Packages
partof: scala-tour

num: 36
previous-page: packages-and-imports
---

# Package objects
Often, it is convenient to have definitions accessible accross an entire package, and not need to invent a
name for a wrapper `object` to contain them.

Scala provides package objects as a convenient container shared across an entire package.
{% tabs pkg-obj-vs-top-lvl_1 class=tabs-scala-version %}
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_1 %}
Scala 2 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 objects can even inherit
Scala classes and traits.

> In a future version of Scala 3, package objects will be removed in favor of top level definitions.

By convention, the source code for a package object is usually put in a source file named `package.scala`.

Each package is allowed to have one package object. Any definitions placed in a package object are considered
members of the package itself.

{% endtab %}
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_1 %}
In Scala 3, any kind of definition can be declared at the top level of a package. For example, classes, enums,
methods and variables.

Any definitions placed at the top level of a package are considered members of the package itself.

> In Scala 2 top-level method, type and variable definitions had to be wrapped in a **package object**.
> These are still usable in Scala 3 for backwards compatibility. You can see how they work by switching tabs.

{% endtab %}
{% endtabs %}

See example below. Assume first a class `Fruit` and three `Fruit` objects in a package
`gardening.fruits`:


{% tabs pkg-obj-vs-top-lvl_2 %}
{% tab 'Scala 2 and 3' for=pkg-obj-vs-top-lvl_2 %}
```
// in file gardening/fruits/Fruit.scala
package gardening.fruits
Expand All @@ -33,10 +54,15 @@ object Apple extends Fruit("Apple", "green")
object Plum extends Fruit("Plum", "blue")
object Banana extends Fruit("Banana", "yellow")
```
{% endtab %}
{% endtabs %}

Now assume you want to place a variable `planted` and a method `showFruit` directly into package `gardening.fruits`.
Here's how this is done:

{% tabs pkg-obj-vs-top-lvl_3 class=tabs-scala-version %}
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_3 %}

```
// in file gardening/fruits/package.scala
package gardening
Expand All @@ -47,13 +73,29 @@ package object fruits {
}
}
```
{% endtab %}
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_3 %}

```
// in file gardening/fruits/package.scala
package gardening.fruits

val planted = List(Apple, Plum, Banana)
def showFruit(fruit: Fruit): Unit =
println(s"${fruit.name}s are ${fruit.color}")
```
{% endtab %}
{% endtabs %}

As an example of how to use this, the following object `PrintPlanted` imports `planted` and `showFruit` in exactly the same
way it imports class `Fruit`, using a wildcard import on package gardening.fruits:
As an example of how to use this, the following program imports `planted` and `showFruit` in exactly the same
way it imports class `Fruit`, using a wildcard import on package `gardening.fruits`:

{% tabs pkg-obj-vs-top-lvl_4 class=tabs-scala-version %}
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_4 %}
```
// in file PrintPlanted.scala
import gardening.fruits._

object PrintPlanted {
def main(args: Array[String]): Unit = {
for (fruit <- planted) {
Expand All @@ -62,11 +104,53 @@ object PrintPlanted {
}
}
```
{% endtab %}
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_4 %}
```
// in file printPlanted.scala
import gardening.fruits.*

Package objects are like other objects, which means you can use inheritance for building them. For example, one might mix in a couple of traits:
@main def printPlanted(): Unit =
for fruit <- planted do
showFruit(fruit)
```
{% endtab %}
{% endtabs %}

### Aggregating Several Definitions at the Package Level

Often, your project may have several reusable definitions defined in various modules, that you
wish to aggregate at the top level of a package.

For example, some helper methods in the trait `FruitHelpers` and
some term/type aliases in trait `FruitAliases`. Here is how you can put all their definitions at the level of the `fruit`
package:

{% tabs pkg-obj-vs-top-lvl_5 class=tabs-scala-version %}
{% tab 'Scala 2' for=pkg-obj-vs-top-lvl_5 %}

Package objects are like other objects, which means you can use inheritance for building them.
So here we mix in the helper traits as parents of the package object.

```
package object fruits extends FruitAliases with FruitHelpers {
// helpers and variables follows here
}
package gardening

// `fruits` instead inherits its members from its parents.
package object fruits extends FruitAliases with FruitHelpers
```
{% endtab %}
{% tab 'Scala 3' for=pkg-obj-vs-top-lvl_5 %}

In Scala 3, it is preferred to use `export` to compose members from several objects into a single scope.
Here we define private objects that mix in the helper traits, then export their members at the top level:

```
package gardening.fruits

private object FruitAliases extends FruitAliases
private object FruitHelpers extends FruitHelpers

export FruitHelpers.*, FruitAliases.*
```
{% endtab %}
{% endtabs %}
3 changes: 2 additions & 1 deletion _tour/packages-and-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ One convention is to name the package the same as the directory containing the S
UserPreferences.scala
- test
```
Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by using braces:
Notice how the `users` directory is within the `scala` directory and how there are multiple Scala files within the package. Each Scala file in the package could have the same package declaration. The other way to declare packages is by nesting them inside each other:

{% tabs packages-and-imports_2 class=tabs-scala-version %}
{% tab 'Scala 2' for=packages-and-imports_2 %}
Expand All @@ -58,6 +58,7 @@ package users {
package users:
package administrators:
class NormalUser

package normalusers:
class NormalUser
```
Expand Down