简体   繁体   中英

Why are classes inside Scala package objects dispreferred?

Starting with 2.10, -Xlint complains about classes defined inside of package objects. But why? Defining a class inside a package object should be exactly equivalent to defining the classes inside of a separate package with the same name, except a lot more convenient.

In my opinion, one of the serious design flaws in Scala is the inability to put anything other than a class-like entity (eg variable declarations, function definitions) at top level of a file. Instead, you're forced to put them into a separate ''package object'' (often in package.scala ), separate from the rest of the code that they belong with and violating a basic programming rule which is that conceptually related code should be physically related as well. I don't see any reason why Scala can't conceptually allow anything at top level that it allows at lower levels, and anything non-class-like automatically gets placed into the package object, so that users never have to worry about it.

For example, in my case I have a util package, and under it I have a number of subpackages ( util.io , util.text , util.time , util.os , util.math , util.distances , etc.) that group heterogeneous collections of functions, classes and sometimes variables that are semantically related. I currently store all the various functions, classes, etc. in a package object sitting in a file called io.scala or text.scala or whatever, in the util directory. This works great and it's very convenient because of the way functions and classes can be mixed, eg I can do something like:

package object math {
  // Coordinates on a sphere

  case class SphereCoord(lat: Double, long: Double) { ... }

  // great-circle distance between two points
  def spheredist(a: SphereCoord, b: SphereCoord) = ...

  // Area of rectangle running along latitude/longitude lines
  def rectArea(topleft: SphereCoord, botright: SphereCoord) = ...

  // ...
  // ...

  // Exact-decimal functions
  class DecimalInexactError extends Exception

  // Format floating point value in decimal, error if can't do exactly
  formatDecimalExactly(val num: Double) = ...

  // ...
  // ...
}

Without this, I would have to split the code up inconveniently according to fun vs. class rather than by semantics. The alternative, I suppose, is to put them in a normal object -- kind of defeating the purpose of having package objects in the first place.

But why? Defining a class inside a package object should be exactly equivalent to defining the classes inside of a separate package with the same name,

Precisely. The semantics are (currently) the same, so if you favor defining a class inside a package object, there should be a good reason. But the reality is that there is at least one good reason no to (keep reading).

except a lot more convenient

How is that more convenient? If you are doing this:

package object mypkg {
  class MyClass
}

You can just as well do the following:

package mypkg {
  class MyClass
}

You'll even save a few characters in the process :)

Now, a good and concrete reason not to go overboard with package objects is that while packages are open , package objects are not . A common scenario would be to have your code dispatched among several projects, with each project defining classes in the same package. No problem here. On the other hand, a package object is (like any object) closed (as the spec puts it "There can be only one package object per package"). In other words, you will only be able to define a package object in one of your projects. If you attempt to define a package object for the same package in two distinct projects, bad things will happen, as you will effectively end up with two distinct versions of the same JVM class (n our case you would end up with two "mypkg.class" files). Depending on the cases you might end up with the compiler complaining that it cannot find something that you defined in the first version of your package object, or get a "bad symbolic reference" error, or potentially even a runtime error. This is a general limitation of package objects, so you have to be aware of it. In the case of defining classes inside a package object, the solution is simple: don't do it (given that you won't gain anything substantial compared to just defining the class as a top level). For type aliase, vals and vars, we don't have such a luxuary, so in this case it is a matter of weighing whether the syntactic convenience (compared to defining them in an object) is worth it, and then take care not to define duplicate package objects.

I have not found a good answer to why this semantically equivalent operation would generate a lint warning. Methinks this is a lint bug. The only thing that I have found that must not be placed inside a package object (vs inside a plain package) is an object that implements main (or extends App ).

Note that -Xlint also complains about implicit classes declared inside package objects, even though they cannot be declared at package scope. (See http://docs.scala-lang.org/overviews/core/implicit-classes.html for the rules on implicit classes.)

I figured out a trick that allows for all the benefits of package objects without the complaints about deprecation. In place of

package object foo {
  ...
}

you can do

protected class FooPackage {
  ...
}

package object foo extends FooPackage { }

Works the same but no complaint. Clear sign that the complaint itself is bogus.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM