Scala: ¿Las clases que extienden un rasgo siempre toman las propiedades de los rasgos?

Dado lo siguiente:

class TestClass extends TestTrait {
  def doesSomething() = methodValue + intValue

trait TestTrait {
  val intValue = 4
  val unusedValue = 5
  def methodValue = "method"
  def unusedMethod = "unused method"

When the above code runs, will TestClass actually have memory allocated to unusedValue or unusedMethod? I've used javap and I know that there exists an unusedValue and an unusedMethod, but I cannot determine if they are actually populated with any sort of state or memory allocation.

Basically, I'm trying to understand if a class ALWAYS gets all that a trait provides, or if the compiler is smart enough to only provide what the class actually uses from the trait?

If a trait always imposes itself on a class, it seems like it could be inefficient, since I expect many programmers will use traits as mixins and therefore wasting memory everywhere.

Thanks to all who read and help me get to the bottom of this!

preguntado el 28 de agosto de 11 a las 04:08

I learned a lot from the answers, and I summed up my findings here: Scala Traits: The Double Edged Sword -

in addition to answers, if you need such kind of optimization, take a look at things like -

2 Respuestas

Generally speaking, in languages like Scala and Java and C++, each clase has a table of pointers to its instance methods. If your question is whether the Scala compiler will allocate slots in the method table for unusedMethod then I would say yes it should.

I think your question is whether the Scala compiler will look at the body of TestClass and say "whoa, I only see uses of methodValue y intValue, so being a good compiler I'm going to refrain from allocating space in TestClass's method table for unusedMethod. But it can't really do this in general. The reason is, TestClass will be compiled into a class file TestClass.class and this class may be used in a library by programmers that you don't even know.

And what will they want to do with your class? This:

var x = new TestClass();

See, the thing is the compiler can't predict who is going to use this class in the future, so it puts all methods into its method table, even the ones not called by other methods in the class. This applies to methods declared in the class or picked up via an implemented trait.

If you expect the compiler to do global system-wide static analysis and optimization over a fixed, closed system then I suppose in theory it could whittle away such things, but I suspect that would be a very expensive optimization and not really worth it. If you need this kind of memory savings you would be better off writing smaller traits on your own. :)

Respondido 28 ago 11, 09:08

So it sounds like small, concise traits are suggested, and perhaps implementing a companion object as a mechanism for dependency injection? I guess that makes sense. - Sean Freitag

I think so yes. Sin embargo, remember that these method tables are implemented per class, and there is usually only one instance of a class object around anyway. So even if you create millions of objects of class TestClass there is only one method table in TestClass. Bloated objects result from too many fields, not too many methods. - Ray Toal

If I were to sum up the above comment, it would seem like using def instead of val/var would be preferred in traits if considering memory. Is this a fair generalization? - Sean Freitag

Actually in Scala, when you use val in a trait, scalac creates a method anyway. You don't really have fields like you do in Java. They are there but you can only see them with javap -c (as you probably already know). Do try this though; it is interesting. You will see calls to TestTrait$class.methodValue inside the decompiled bodies of TestClass. - Ray Toal

A var clearly needs to allocate space in each instance, because of mutable state. What about a val, is the language restrictive enough that these don't have to be allocated per instance? Certainly a private val should be shared between all instances of a class. - Kipton Barros

It may be easiest to think about how Scala implements traits at the JVM level:

  • An interface is generated with the same name as the trait, containing all the trait's method signatures
  • If the trait contains only abstract methods, then nothing more is needed
  • If the trait contains any concrete methods, then the definition of these will be copied into any class that mixes in the trait
  • Any vals/vars will also get copied verbatim

It's also worth noting how a hypothetical var bippy: Int is implemented in equivalent java:

private int bippy; //backing field
public int bippy() { return this.bippy; } //getter
public void bippy_$eq(int x) { this.bippy = x; } //setter

Para val, the backing field is final and no setter is generated

When mixing-in a trait, the compiler doesn't analyse usage. For one thing, this would break the contract made by the interface. It would also take an unacceptably long time to perform such an analysis. This means that you always inherit the cost of the backing fields from any vals/vars that get mixed in.

As you already hinted, if this is a problem then the solution is just use defs in your traits.

There are several other benefits to such an approach and, thanks to the uniform access principle, you can always override such a method with a val further down in the inheritance hierarchy if you need to.

Respondido 28 ago 11, 14:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.