Plan for Package Objects in Scala 3

Dropped: Package Objects says package objects will be dropped. But there’s three areas which the replacement (top level definitions) does not suffice:

  1. Top-level definitions does not allow statements like println(1). Or it does, but you need to wrap them in val _ = println(1). This seems like a pretty silly restriction with a pretty silly workaround.

    • Given we already allow top-level wrapper objects to have non-lazy initialization logic with vals, we should just allow top level statements. Apart from improving consistency and removing a weird restriction/workaround, this would also make vanilla .scala files to be usable for scripts without custom wrapping like Ammonite and Scala-CLI do. Other typed/compiled languages like C# allow top level statements without issue, in scripting languages like Python/Ruby/Javascript this is the default, and of course Ammonite/Scala-CLI demonstrate there’s a broad use case even in Scala (and .kts in Kotlin or .fsi in Fsharp)
  2. Top-level wrapper objects cannot inherit from traits or classes. This is pretty common in my experience, e.g. having package object foo extends Thing have the default instance of something and package foo; object Bar extends Thing and package foo; object Qux extends Thing have other available instances, all of which implement the same interface and inherit the same implementations.

    • One limitation of doing this is that you can’t do val x = foo when foo is a package object, as it complains package foo is not a value, and you have to say val x = foo.package. This seems like a restriction that should be liftable right, to make val x = foo automatically expand to val x = foo.package? That would help smooth out one of the warts of package objects today
  3. package objects serve as a natural place to put package-level Scaladoc and other documentation, similar to module-info.java files or python __init__.py files. Without that, there’s nowhere to put “package scaladoc”. readme.md files work fine when browsing the repo but are not slurped up by scaladoc.

All of these seem fixable, but thinking further, what is a package object other than object package; export package.*? I understand having top-level definitions is great, but given that package objects seem to have a pretty trivial desugaring in terms of other existing language features, it seems like the cost of keeping them around indefinitely for backwards compat isn’t too much. And other languages have module-info.java or __init__.py files that serve similar purposes, which indicates to me that the idea of a “primary file for a folder full of files” does have value as a general abstraction

6 Likes

There was a Scala 2 REPL that used package objects as the container for lines.

The limitation (as your recent issue filed with Scala 3 points out) is the peculiar semantics of statically reachable objects. That includes class initialization and deadlocks when multithreading.

The other conceptual challenge for modelling scripts is whether the code is seen as a template (body of a class or object) or local (body of a method or function).

There are so many trade-offs that the fiction of a “snippet” requires explicit choices about context (wrapper).

Package objects were conceived with a footnote that they could be developed for module definition (meaning the public interface of the package). That was not pursued, but I think led to a long period in which the feature was buggy and underspecified. So I don’t think they have a trivial desugaring.

Recently, it occurred to me (while jogging) that package objects could be revived just for module definitions for JVM module support. The goal would be to avoid the directives syntax in module-info.

Maybe also to avoid //> directives syntax, which might seem inevitable.

There is also the longtime need to document a package and also deprecate a package.

Edit: this morning, there is another ticket on Scala 3 about “why doesn’t my code work in a top-level object?” In this case, the customer used App, which subverts the issue on Scala 2 which does the DelayedInit rewrite, but remains broken on Scala 3. I say broken because even though it “works as expected”, the UX/DX is very poor.

1 Like

There is a 4 years old issue with regard to package object for package-level documentation.