One of the interesting features of Scala is the trait construct. In Java you have interfaces, a stripped down class that lets you express the requirements for classes that implement it to have a given … interface. Just to keep this from an OO perspective – in a certain system all MovableObjects, must expose an interface to retrieve, say, position, speed and acceleration:
interface MovableObject { Point getPosition(); Vector getSpeed(); Vector getAcceleration(); } class Rocket implements MovableObject { public Point getPosition() { return m_position; } public Vector getSpeed() { return m_speed; } public Vector getAcceleration() { return m_acceleration; } private Point m_position; private Vector m_speed; private Vector m_acceleration; }
In Java the interface has no implementation part, just a list of methods that heir(s) must implement. This is a way to mitigate the lack of multiple inheritance in the Java language – a class can inherit at most from a single base class, but can implement as many interfaces as needed.
Scala traits are different in the sense they can provide an implementation part and fields. Now that I read the wikipedia definition, it looks like that traits are a bit more mathematically sound than interfaces (that’s what you can expect in functional language). Anyway, in plain English, traits are very close to C++ base classes (with some additions that is stuff for another blog post) in the sense that you can both require interface and provide common functionality.
So in my last coding-storm (that’s what my current work is like, a lot of paperwork, meetings, interviews, reviews, whatever, interspersed with short burst of intense coding sessions) I had the need to implement a reusable component with a number of parameters. It seemed like a good idea (or maybe I was biased by a former implementation) to make it a trait.
Unfortunately, unless you are using dotty, Scala traits can be parametrized only with types, but there is no way of “constructing them” with parameters. Note, just to clarify the point – it is fine to write:
trait Foo { val x : Int = 3 }
But there’s nothing like:
trait Foo( x : Int ) { }
Google pointed me to this stack overflow question/answer where the poster asked what is the expected way to pass parameters to a trait and the most voted answer suggested either to use an abstract class (instead of a trait), or to have virtual functions that provide parameters from the heir.
The first solution limits the scope of my component since Scala has no multiple inheritance just like Java. The second solution looked somewhat better to me. So I wrote something like:
trait Foo { val p : Int // this is a parameter val y : Int = 2*p // this is a derived field } class Heir extends Foo { val p = 3 }
At a first glance nothing is wrong and this seems to solve the problem, unless it actually doesn’t.
Add a method to Foo to print y and then invoke it in scala REPL and you’ll be surprised to read 0 (zero). Let’s rework this and write it using functions instead of fields:
trait Foo { def p : Int // this is the parameter val y : Int = 2*p } class Heir extends Foo { override def p : Int = 3 }
This works as expected… but let’s dig a bit deeper in what happens. Regardless of the language the code generated by the compiler first builds the base class, then the derived, one at time, in the order they appear in the inheritance hierarchy. This is a natural way of constructing objects, much like to build the first floor before the second one in house building.
In the example, when you call new Heir
, the code first builds Foo instance and then Heir instance. On building Foo instance, value y is assigned to 2 times p. In the first case p is a field, it will be assigned by Heir constructor, so now it holds the default value (zero). Two times zero is still zero, so y becomes … well, zero.
In the second example p is a virtual function, so when y is constructed a virtual call is performed. The object is an instance of Heir so Heir.p is called, 3 is returned and finally y is assigned to 3×2=6.
So far so good, but … are we sure?
When the virtual call is performed the Heir constructor has not yet been called. That means that the specialized part of the object has not yet been initialized. But some code is executed inside an uninitialized context. Even if the JVM mitigates the trouble you are going to, by always providing a default value for variables, if your p() depends on, say, a reference in Heir, a NPE would happen as soon as a Heir object is instantiated.
What happens in other languages? In C++ it is fairly known that late binding is not used in the constructors. When you call something in a C++ constructor you are ensured that you are calling either in a subclass or in the class you are building (well refer to the link above for a more correct and comprehensive description).
C# behaves just like Java – the virtual call is performed accessing a not-yet initialized part of the object.
I find that – at least once – C++ errs on the side of safety. I think that, despite of what the programmer expect, the safest conduct is to avoid calling a method on an uninitialized object (otherwise there would be no need for constructors). C++ does the safest, but in a subtle way. No warning, no error for this code. Scala, Java and C# do the unsafest choice, yet in a subtle way. No warning and no error are produced.
I don’t know what happen in other languages, but I feel that the less subtle this choice is, the better chances are to avoid errors.
In C++ I would force the caller to qualify the function, I mean something like:
class Foo { public: Foo() { Foo::f(); } virtual void f() { // do something. } };
so that the intent of the writer is clear and the scope operator serves as a memento to where that call is bound. Failing to write like that, the programmer should be served with an error.
I can’t stop considering Scala, Java and C# approach flawed and not designed for good software engineering. Especially when thinking about Java, where everything has been dumbed down, every corner has been rounded and softened, so that no programmer could hurt themselves, it really puzzles me that this sharp finger-cutting pitfall is there.