An Introduction to the Tingle Programming Language

Dirk van Deun, dirk at dinf.vub.ac.be

Section 4: Programming Style

If the previous section leaves you with the impression that scoping for methods many levels deep will be awfully complex, this section is all about avoiding deep nestings. That does help in understanding programs; however the real reason to avoid nesting wherever possible is to promote reusability. (Every nesting is a dependency.)

We can emulate boring old single inheritance in Tingle:

as car {
   def start() { ... }
   def stop() { ... }
}

as car {
   as taxi {
      def answer_call() { ... stop() ... start() ... }
   }
}

class Car = car.
class Taxi = taxi car.

This is all very well, but emulating multiple inheritance would lead to overly deep nesting: note that we only nest with ideas, not with classes. (Classes have no internal order; and if they would have one, that one would probably not always be the appropriate one.) A "literal translation" of code with multiple inheritance would therefore be ugly, and its scoping complex:

as car {
   as carGUIObject {
      def draw() { ... }
   }
}

as car {
   as carGUIObject { // or should taxi come first ?
      as taxi {
         as taxiGUIObject {
            def draw() { ... super.draw() ... }
         }
      }
   }
}

class CarGUIObject = car carGUIObject.
class TaxiGUIObject = taxi car taxiGUIObject carGUIObject.

There is a better way, with shallow scoping, and all draw() methods snugly together in the source code; you do this by introducing only one new idea for the concern "drawing":

as GUIdraw {
   as car {
      def draw() { ... }
      as taxi {
         def draw() { ... super.draw() ... }
      }
   }
}

class CarGUIObject = GUIdraw Car.
class TaxiGUIObject = GUIdraw Taxi.  // equivalent to GUIDraw taxi car

This shows that there is no dominant decomposition by the sort of the vehicle, and also that the "diamond problem" does not exist here.

Note that, if we want to add another concern later, we can write this up without taking GUIdraw in scope, when the two concerns are independent of each other:

as accounting {
   as car {
      def costs() { ... }
      as taxi {
         def costs() { ... }
      }
   }
   as employee {
      ...
   }
}

Again, scoping is shallow. We can compose all this with:

class CompanyCar = accounting GUIdraw Car.
class CompanyTaxi = accounting GUIdraw Taxi.

All these good things come with a trade-off: in a system based on intersecting ideas, emulating "subclassing for construction" or "subclassing for combination" is the way madness lies. The relationship between a class and its parts should always be like "is a" or "is": never "has a" or "is a bit like a". (Use an instance variable to express "has a" relationships.)

We conclude this section with another, more superficial example of programming style: a rather aesthetically pleasing way to write what in traditional object oriented thinking would be ugly subclasses that are smaller than their superclass:

class Ellipse = generic ellipse.

as ellipse {
   var colour, axis
   as generic {
      var second_axis
      def area() { ... }
   }
}

Ellipse is written in this manner so that later we can easily and correctly derive:

class Circle = circular ellipse.

as ellipse {
   as circular {
      def area() { ... }
   }
}



Contents | Section 5: Mixins