Scala 3: Package objeccts are deprecated, how to create types in a top-level package and use them everywhere in your project

As a brief Scala note today, in Scala 3 it doesn’t look like there’s a way to use something like a package object to make some initial code available to all sub-packages of a high-level package. That is, I’d like to be able to define some types in a top-level package like this:

com.aa.myapp

and then use it in all sub-packages like these:

com.aa.myapp.model
com.aa.myapp.view
com.aa.myapp.controller

You might be able to still do that with Scala 2 package objects, but those are deprecated in Scala 3 and may go away at any time.

Solution

What I have found that you can do instead is define your code in a “near” top-level package like this:

package com.aa.myapp.predef   // <-- note this package name

// i want to use my type names in all ZIO expressions,
// such as writing `ZIO[NoEnv, NoErrors, NoReturnVal]` instead of
// `ZIO[Any, Nothing, Nothing]`:
type NoEnv = Any
type NoErrors = Nothing
type NoReturnVal = Nothing

Then you can import those package contents into your other files:

package com.aa.myapp.model       // <-- note this package name

import zio.*
import zio.Console.*
import com.aa.myapp.predef.*     // <-- and the import statement

// some ZIO code in here, like this:
val blueprint: ZIO[NoEnv, NumberFormatException, Int] =
    for
        a <- zMakeInt("1")
        b <- zMakeInt("uh oh")
        c <- zMakeInt("3")
    yield
        a + b + c

This has the benefit that it’s more obvious where those types come from, at the expense of having to add an additional import line everywhere you want to use those new types.

Discussion

Personally, I think I would like to be able to have something like a Predef.scala file at the root level of my application, where I can define all my custom types for the entire project, but I haven’t thought about what the potentially negative impacts of that could be.

But if you think about it, Scala built-in types like String, Int, and functions like println all come from somewhere, and they are in a Scala Predef.scala file, so it seems like having a Predef.scala file for each project could be a good thing.