A JScript .NET Design Donnybrook, Part Two

I am totally amused that the comments on yesterday's entry are nigh-isomorphic to the argument that we had over this in October 2000. As noted, f.bar(f); does in fact call the base class function, not the derived class function as some might expect.

I was on the fence back in 2000, but leaning towards the derived class, as was most of the rest of the team. Peter Torr was leaning towards the base class, and Herman, the architect, was pretty clearly in the base class camp. My argument was pretty similar to the arguments that most of you guys raised yesterday. In short, I cleaved to the principle:

Calls to methods on objects of unknown type should have the same semantics as if the type was known to be the most derived type. If developers want to restrict a call to a method on a particular type, they can add the annotation.

Though that is clearly a decent principle, in this situation it conflicts with some other design principles, such as:

JScript is all about guessing what the developer meant to say, and muddling on through if it's not clear.

and

Class-based code is a more-strictly-typed alternative to dynamic prototype-based code, and therefore class semantics should opt for increased type safety and efficiency rather than increased dynamism.

It really comes down to guessing what the developer meant. My example was deliberately abstract. Consider a more realistic example. In Canada, there's a Goods and Services Tax which is rather complicated. There are situations where a cake is taxable if you sell it in a restaurant, but not taxable if you sell it in a grocery store. (The rules are considerably more complex than that -- my father owned a restaurant at the time this tax became law, and figuring out when a single muffin vs a box of muffins was taxable was quite tricky.)

class Grocery {
function get tax() {
return 0 ;
}

function PrintTaxrate(item) {
print(item.tax);
}
}

class Cake extends Grocery {
hide function get tax() {
return 7;
}
}

Here the meaning of the "hide" is "if a cake is being treated as a cake, it's taxable. If it is being treated as a grocery item, it's not taxable."

But uh-oh, the developer of the base class did not annotate the method as taking a Grocery.

Now, suppose that you are developing the Cake class. The Grocery class already exists, and you are extending it -- perhaps the base class is even in an entirely different package. The developer of the Grocery class has forgotten to annotate the method, not anticipating that someone might have a non-virtual override. In order to enable the developer of the Cake class to implement the desired semantics, the code gen for the Grocery class must as its first guess assume that the caller intended this thing to take a Grocery.

Clearly there are arguments on both sides; it's a judgment call whenever you have to guess what the user meant.

Another advantage of this approach is that it is more efficient. Calling late-bound is very inefficient compared to speculatively casting the object to a class, and calling the method if the cast succeeds. The cost of late binding is enormous, and we wanted to do everything possible to eliminate it while still keeping the language scripty.

Therefore what the code gen does in this case is looks for every visible class which has the appropriate method, and generates a cast-and-call for each, and finally bottoms out to do the late-bound call if none of the casts worked. Unless you have a large number of visible classes with the same method names, this is pretty darn efficient. If you choose to always call the most derived type's method then there is no way to generate this code efficiently because you always must do the late bound call first.

Philip Rieck's head exploded shortly after he discovered that the generated code actually checks to see if the argument is an instance of base, and THEN checks to see if it is an instance of derived. Isn't that unreachable code? Put your head back together Philip. Yes, it appears to be a codegen bug. I can't think of any circumstance in which that would not be dead code. I'll mention it to the JScript .NET team the next time I see him.

Peter Torr might remember more reasons than I do why we chose to do it this way. Peter?