Sorry to revivify, but I promise to inject fresh content or context.
I happen to be tweaking Scala 2 messaging for package objects, was bored and noticed an unread topic.
Dotty has deferred deprecating package objects (which are replaced by “top-level definitions”), but no longer supports looking up implicits in prefixes of a type which are package objects.
To review, implicits are searched for twice, first in lexical scope (enclosing class and package), and if that fails, second in “implicit scope”, which means parents of a class and also of type args, so for List[C]
, companion objects of List
and of C
. Implicit scope of an inner class C#D
includes companions of both D
and C
. If the full name was p.q.C
, it includes packages p
and q
, which means their package objects, in Scala 2. The packages in the prefix of C
are dropped for Scala 3.
2.13.12 warns under -Xsource:3
if a package object extends a class, because of an earlier expectation that the feature would be removed, and that dotty would remove package objects altogether. Today’s tweak is to make it warn only for actually disappeared feature, namely, package prefixes in implicit scope.
Enclosing packages as lexical scope are unaffected, but the usual rules of visibility apply, as described in this thread.
To answer the question about style, excessively nested package clauses can indeed make it difficult to see where a named class comes from, let alone an implicit. For example, a common package name like util
might be problematic: first, you wonder which package does util
refer to; second, it might safely refer to a particular package but not after changing package clauses.
To say more, with imports, you get precedence and shadowing, so you can introduce widgets.util
when you need it.
I hoped to find some guidance, but I see scala.collection.immutable
demonstrates all permutations.
Some files used to say
package strawman
package collection
after the experimental or development name, so it’s partly for historical reasons.
I’d propose standardizing via a formatting tool that project files have a certain header and a certain top-level package, below which components are permitted to innovate. That opinion is not based in science, but in years of annoyance accumulated in the nervous system like a toxin.
To illustrate the question about overloading, this works with an ordinary object and import, but not with a package object:
trait T {
def f(i: Int) = i.toString
}
trait U {
def f(s: String) = s*2
}
package object p extends T with U
package p {
object Test extends App {
println(f(42))
}
}
errors with
pkg-overload.scala:12: error: type mismatch;
found : Int(42)
required: String
println(f(42))
^
1 error
wait, let me try
package q {
import p.`package`._
object Test extends App {
println(f(42))
}
}
which works, so just look-up from inside the package is broken in Scala 2.
Everything works in Scala 3, except the traits must be in a package, for visibility.
There may be other problems, for example, with incremental compilation.