One thing we should keep in mind here is that there is a huge difference between the way say Java looks at imports and packages, and the way Scala does.
In Java, packages (like a lot of things) are “special.” They aren’t similar to anything else and simply have their own rules, including the ability to import things from them. Similarly, fields and methods are two completely different things.
In Scala, there are only two namespaces, terms and types. Packages, singleton objects, and vals/vars/defs (whether class members or local), are all just members of the namespace of terms. And importing is completely orthogonal: You can import any member of any stable identifier. So unlike Java, where the “argument” to the import statement is unique syntax, in Scala one can make the case that import
's argument is … ok, not simply a path … but a “path pattern.” (Not quite “pattern” as in pattern syntax, as used in pattern matching, for comprehensions, and definitions, but something sort of analogous to it.) So System.identityHashCode(x)
and { import System.identityHashCode; identityHashCode(x) }
are both using the same rules, except that one is a superset of the other. In other words import
is basically saying `any path that follows the following pattern is in scope and as if one had written it.
That was a bit rambly, but the takeaway is:
-
@lihaoyi’s point that absolute vs. relative syntax should be consistent between import and expressions is not just convenience or elegance, it’s a lore more fundamental than that to the language. (Not saying it’s mandatary, but there’s a strong case for it.)
-
@olafurpg’s suggestion breaks this, unless from now on
_every single identifier
has to be prefixed with something. If you write val x = 10; println(x)
and not println(.x)
, then x
should be the equivalent of currently writing _root_.x
.
Another counterargument to @olafurpg’s perspective (which flows from #2 above) is that always in programming, an unqualified identifier is relative, and we prefix to give more context. To illustrate:
object A {
val x = 1
val y = 2
object B {
val x = 3
// Now: If I want to refer to B.x, the norm is to just write x. If I want to refer to A.x,
// well I have to write out A.x because of shadowing, but even for y, there's
// more, not less, of a chance I'd want to write A.y instead of just y. Arguably,
// shadowing only makes sense because we already see things that way
// in the first place!
}
}
The above holds (approximately, at least) whether A and B are packages, objects, or methods (except that then you can’t qualify by them).
The takeaway there is that when it comes to qualifying things in code, we always think from innermost outwards. So even though you can make the argument about taxing things you want to discourage, in reality it’s a lot more logical to make absolute explicit and relative default.