April 7th, 2010

Convenience features, part 1

The two themes of the upcoming release are dynamic coding and convenience features.  I've started into the dynamic stuff already, so I'll turn to the convenience features, starting with one of the big new items: operator overloading.  If you haven't run into this term before, it's the ability to define a custom meaning for an algebraic operator when applied to a particular combination of types.  The canonical example in C++ is to create a Complex class to represent complex numbers, then define the meanings of Complex + Complex, Complex * Complex, etc. 

I've always been pretty negative about operator overloading in C++, which is probably the most widely used language with this feature.  In practice, that idea that overloading is mostly for extended arithmetic types goes out the window.  What people really do with overloading in C++ is use it for shorthand syntax for object methods.  The standard C++ library leads the way, by defining << and >> as input and output operators for its stream classes.  There's a certain visual logic to that, but it doesn't really make any sense as an extension of the normal bit-shift meaning of those operators.  That sort of usage seems to me to obfuscate code rather than clarify it.  Anyway, I've always shied away from it in C++.

However, in a TADS context, I've run into enough situations where overloading would be useful that I've warmed up to it.  And the thing is, TADS already has lots of overloaded operators - it's just that they've only been definable inside the system.  For example, "+" has different meanings for integers, strings, and lists - all completely different meanings, but they're intuitive extensions of the general concept of addition.  Given that overloading is already entrenched in the system, it's not a huge leap to want to use it within custom game-defined objects.

For example, suppose you wanted to create an object that represents the set of objects in scope, and then you want to be able to pass the object to a routine that expects an ordinary list.  The straightforward way of doing that is to create an actual list object.  But that's potentially inefficient, since now you have two copies of the scope set, one as the custom object and the other as this new list.  It's also harder to use, because you have to manually do the conversion.  It would be nicer if we could just use the scope set object as though it were a list - so that it mimics a regular list object to any code that only wants an ordinary list, but has additional features (i.e., methods) that can be used in other code written to use this custom object.  This is where the operator overloading comes in.  To mimic an ordinary list, the custom object has to be able to define what obj[index] means.

The new TADS syntax for operator overloading looks a lot like the C++ syntax.  You basically just define a method with a special name, consisting of the new keyword 'operator' plus the operator you're defining.

  class ScopeList: object
    operator [](index) { return ... }

If 'x' is a ScopeList instance, then, evaluating x[i] will call x.operator[](i).

It's definitely an abusable feature; when it's used purely for syntactic concision, as it often seems to be in C++, it can certainly make code harder to use and read.  But I think on balance it'll be a great addition for library writers, because it'll let libraries create custom classes that are especially easy and intuitive to use because they mimic simpler built-in objects.