THIS PIECE IS FULL OF LIES DO NOT TRUST IT
More accurately, its information is out of date and no longer valid. This describes the old behaviour of the Scala package system. Its behaviour has been different from this for some years now, as it turned out most people weren’t reaching the “acceptance” stage I describe below and after enough shouting the behaviour got changed. This is preserved solely for posterity. Do not rely on it for accurate information.
Original piece follows:
Every now and then someone discovers how packages work in Scala. This process typically passes through a number of stages.
- Confusion: “Hey, guys, I found this weird bug. Can you take a look?”
- Surprise: “What? It works like that? Really?”
- Denial: “No, I don’t believe you. This has to be a bug.”
- Anger: “Dear scala-debate. This is the worst feature in the entire world, and if you don’t agree with me you’re a big poopy head”
- Acceptance: “Actually, this is quite a neat feature”
Not everyone reaches step 5. Many stay in step 4 permanently, often because they’ve discovered that this interacts poorly with certain conventions they use.
This behaviour is particularly unfortunate because actually Scala’s package behaviour is quite nice. But people don’t seem to be willing to believe this and instead make up all sorts of behaviour which it doesn’t have and never has had and then get upset when the reality does not correspond to their fiction.
And so, in the hopes of dispelling some of this confusion, I bring to you the reality of how packages work in Scala. Some of this is very basic material, but I’m presenting it in case you’ve not explicitly thought about it in these terms as it will help with the leadup to the actually important part.
Identifiers
You have a bunch of identifiers in scope. These are names for things. It doesn’t matter what they’re names for: They could be vals, defs, packages, objects, etc. So for example suppose I have:
package foo; object bar; object baz{ val kittens = "kittens"; }
within this file, say within the object bar, we’ve got a bunch of identifiers in scope: We have foo, the package we are in, bar, an object, and baz, another object. We don’t have kittens in scope (except within the object baz).
Within the object baz, everything in scope at the outer level is in scope here, but we’ve introduced the additional identifier kittens.
Note that a package conceptually constitutes one “level”. Everything from your current package is in scope, regardless of how you split it up into files – I could have moved some of the objects above into separate files and nothing would have changed.
Top level identifiers
Packages like foo are “top level” – they live in the global scope. Any file can refer to the identifier foo.
Nesting of packages
In the same way we had an object inside a package and introduced a new scope, we can nest a package inside a package.
package mammals; package rodents{ class Rat; }
This places the package “rodents” inside the package “mammals”. In exactly the same way the object did, this inherits everything from the outer scope (and remember: the scope of the package is the scope of everything
package mammals; class Cat; package rodents{ class Rat{ def flee(moggy : Cat) = println("Help, help! Run away! It's " + moggy) } }
the identifiers of the outer scope are available in the inner one.
But this sort of deeply nested package structure gets very ugly to write, so what one tends to do is seperate it out to one package in a given file, even the nested ones, and so there’s syntax to support it:
package mammals.rodents; class Rat{ def flee(moggy : Cat) = println("Help, help! Run away! It's " + moggy) }
This is exactly the same as the previous example except we’ve moved Cat to another file. It’s still in scope as before.
Members
identifiers can have members. These are other identifiers which live on them and can be accessed with a .
For example, to refer to Rat from the package mammals we would refer to it as rodents.Rat.
Shadowing
You can reintroduce the same identifier at an inner level. Going back to our first example suppose we had written baz as
object baz{ val bar = "kittens" val kittens = bar }
Then kittens would still contain the string “kittens”, as it refers to the definition of bar in the current scope not the outside one. Outside of baz, bar would still refer to the object.
An important aspect of this: You can shadow packages just like anything else!
Suppose we have
package foo{ object baz; package foo{ object baz; object stuff{ val it = foo.baz; } } }
Then “it” points to the innermost baz, not the outermost one: We’ve shadowed the definition of foo.
And this is where the problem lies.
Suppose I have
package net.liftweb{ object AwesomeWebWidget{ def doStuffWith(url : java.io.File) = ... } }
and someone comes along (remember this doesn’t have to be in the same file – it can even be in a jar) and introduces
package net.java.kittens; class Kitten;
Now the lift code will no longer work! The problem is that what we have actually looks like this:
package net{ package java{ package kittens{ class Kitten; } } package liftweb{ object AwesomeWebWidget{ def doStuffWith(url : java.io.File) = ... } } }
the problem is we have a different java identifier in scope than the one we wanted this to mean. It actually refers to the java identifier that we acquire from the net package, rather than the base java that lives in the root as desired. This is the problem that sparked the latest “discussion” in scala-debate on this subject.
The solutions
One thing which everyone immediately leaps to propose is to change the way imports work in Scala. Hopefully the above should have demonstrated that this wouldn’t help: I have not mentioned the word “import” anywhere in this explanation. So we can safely discard this as a non-solution.
The primary current solution is, unfortunately, a bit of an ugly one. When you want to say “the java at the root and I really damn mean it” you can refer to it as _root_.java.io.File. Adding this to your fully qualified names will force it to refer to the right one. Many people have taken to using _root_ on all their imports to fully qualify them. Personally I don’t feel the need (I don’t use Java reverse name conventions though, so I rarely run into the negative aspects of this behaviour).
Some people have taken to fully qualifying all their imports to prevent this sort of accidental shadowing. Personally I find this highly unnecessary. My preferred solution is to avoid the reverse domain name convention: Not having your top level package as something common greatly reduces the ability to accidentally have packages injected into your scope like this.
Other solutions are currently under discussion in scala-debate, so some of this may be prone to change