The new object-oriented syntax for Synergy/DE version 9 is pretty closely modeled after C#, because Synergex plans to create a CLR version of the language and they don’t want the syntax supported on the various platforms to differ by much.
That means, of course, that when we hear “Multiple Inheritance” we are supposed to close our eyes and ears, hold up a cross, load up with silver bullets, and make it go away.
And it’s true that MI has probably caused more problems than it solves in languages that support it. Sometimes the “need” for MI is really a symptom that you should have used composition instead of inheritance (the object “has a” <whatever you’re deriving from> instead of “is a”).
But sometimes MI can be useful. There are abstractions that apply to an object itself that cut across normal inheritance lines. For instance, there are lots of classes that you could compare for equality in some manner more abstract than comparing the identity of the objects. So, you’d like to overload the ==, !=, >, <, >=, and <= operators with methods that compare the objects in some custom way specific to the class. But why write all of those methods for every class that needs them? If each class had one method to compare the objects and return a value indicating less than, equal, or greater than — then all the operator overload methods could call that method, and they’d be essentially identical for each class. If you could inherit that code from a common source, you’d save yourself a lot of typing — and possibly a lot of paste and modify errors.
This idea comes to me from Ruby, which has the concept of “mixin modules” — code that can be added to any class to extend its functionality, without regard for its inheritance hierarchy. It creates a form of MI without all the screaming.
The downloadable code below includes a module in Comparable.dbl. Don’t compile that module, but if you include it within a class definition it will define the comparison operator overloads for you. These methods assume that your class contains a “compare” method that it can call to determine the appropriate result for each operation. Because Synergy/DE is a statically typed language, you also have to give it the types to work with, via .defines. So, for instance, in Person.dbl we have:
class Person
.define COMPARE_CLASS Person
.include "Comparable"
This creates the op_Equality, op_Inequality, and related methods that call the compare method to compare Persons. How do you compare Persons? Why, by their likability, of course:
protected static method compare, int
in req obj1, @Person
in req obj2, @Person
proc
using obj1.likability select
(< obj2.likability), mreturn -1
(> obj2.likability), mreturn 1
endusing
mreturn 0 ;==
endmethod
You might expect that the compare method will look pretty similar from class to class as well, so I also added the ability to generate the compare method automatically to compare a specific member (data or method). Just define COMPARE_MEMBER, as shown in SalesPerson.dbl:
.define COMPARE_CLASS SalesPerson
.define COMPARE_MEMBER annual_sales ;Compare their annual sales
.include "Comparable"
It’s also possible to have different comparisons for mismatched operand types. Just define COMPARE_TYPE2 (and optionally COMPARE_MEMBER2), as when comparing a SalesPerson to a Person.
.define COMPARE_TYPE2 @Person
.undefine COMPARE_MEMBER ;Don't generate "compare"
.include "Comparable"
protected static method compare, int
in req obj1, @SalesPerson
in req obj2, @Person
proc
mreturn -1 ;a SalesPerson is always less than a Person
endmethod
You may wonder why the second argument needs to have the “@” included in the definition, and why I called it COMPARE_TYPE2 instead of COMPARE_CLASS2. This is so you can define comparisons against primitive types like “int” or “string” as well.
The Programmer class in Programmer.dbl shows how the COMPARE_MEMBER definition can resolve to a member function:
.define COMPARE_CLASS Programmer
.define COMPARE_MEMBER geekQuotient()
.include "Comparable"
private method geekQuotient, decimal
proc
begin
data quotient, decimal
quotient = (num_languages * num_languages) * years_coding
if (ever_used_VB) quotient /= ^x(dead)
mreturn quotient
end
endmethod
See testComparable.dbl for examples of usage.
Naturally, you can use the same technique to create comparison operators for any class. As long as your comparison operators have complementary meaning (which I would recommend), you should never have to write your own overloads for them again.
You can expand on this idea to create other inheritable traits that cross inheritance boundaries. For instance, in Ruby, anything that acts vaguely like an array or collection includes “Enumerable” to get a whole host of useful methods.
So SalesPerson “is a” Person, but it also “is a” Comparable object. So is Programmer. Any other class into which you include Comparable also “is a” Comparable, without necessarily being a Person. We’ve achieved MI (Multiple Inheritance) via MI (MixIns), blurring the line between inheritance and composition.
UPDATED 4/28/2008 to add support for comparisons against primitive types, and to automatically undefine COMPARE_TYPE2 and COMPARE_MEMBER2 at the end of Comparable.dbl.
