Tingle


Tingle is an object oriented programming language designed around
one single innovation: the emancipation of scoping of methods from the
composition of classes.  Usually, composition determines scoping:
subclassing adds new components to a class, which can override the
other components and have them in scope; similarly, the order in which
mixins are combined creates a hierarchy for scoping; and a bit out of
the mainstream, the explicit plumbing used with Smalltalk traits does
this in a more flexible manner; but I know of no language in which the
roles are reversed, where scoping determines composition.

This introduction to Tingle should serve as proof that this is
not just a theoretical alternative, but that it can be the basis for a
viable programming language.  It will show how classes can compose
without an explicit order and without explicit plumbing, by just
"clicking together" in a way that is determined by the lexical scopes
of their individual methods.  It will also present the beginnings of a
programming style that makes good use of such a language.

The first five sections are about programming in the small.  Sections
I and II each introduce one basic semantic rule for the language;
and sections III, IV and V will show how these two rules allow for a
novel programming style, as well as for expressing mixins and
templates, and emulating good old subclassing.  The rest of the text
is mainly about programming in the large.  In particular, section VI
is about how name spaces influence class composition as it was
described in section I, and section VII adds to the scoping rule from
section II to allow the creation of reusable black boxes.  Section
VIII adapts the well-known concepts of "public", "private" and
"protected" to the language, and section IX adapts Scala companion
objects.  Section X wraps up with some general remarks.

A description of more features of the current prototype version of
Tingle can be found at http://dirk.rave.org/tingle/.


I  The Predominant Method

Let us define a simple class Human as follows:

class Human {
   def play_tetris() { ... }
}

Now in languages with subclassing, to derive a subclass Woman, we
would, using some specialized syntax like "extends", reopen the scope
of Human, and embed the class Woman inside it, so that the methods
and data of the class Human would be visible for any newly added
methods for Woman.  Abusing Tingle syntax, as the language does not
allow of subclasses, we would write something like:

class Human {
   class Woman {
      def buy_more_shoes() { ... }
      def give_birth() { ... }
   }
}

The two new methods are able to use the Human method play_tetris(),
which is in an enclosing scope (for instance to while away the
time until a shoe salesperson is available).

In our language without subclassing, the above code is syntactically
valid; but it actually means that the classes Human and Woman overlap,
and that the two new methods exist in their intersection, available
only to objects that are both Human and Woman.

As all women are by definition also human, the class Woman and the
intersection coincide.  Maybe we can make the example more interesting
by making it about the intersection of Human and Female instead:

class Human {
   class Female {
      def buy_more_shoes() { ... }
      def give_birth() { ... }
   }
}

class Woman = Human Female

We now have two independent classes, Human and Female, neither of
which is a super- or a subclass of the other; and thus we see that we
need a separate composite class Woman, for objects that are both
Human and Female.

As order is irrelevant for set intersection, we can change the order
of nesting:

class Female {
   class Human {
      def buy_more_shoes() { ... }
      def give_birth() { ... }
   }
}

Now we can express something that we could not express earlier: the
fact that give_birth() is typically female, but buy_more_shoes() only
applies to human females:

class Female {
   def give_birth() { ... }
   class Human {
      def buy_more_shoes() { ... }
   }
}

As might be expected, a more specialized give_birth() for human
females can be added in the intersection.  If we would create an
object of the class Female without composing it with Human, it would
provide the basic method give_birth(), and no buy_more_shoes().  

If we do create an object of the class Woman, on the other hand, all
most specialized methods will be available.  Methods in the
intersection of Female and Human will predominate over methods with
the same name in the composing classes.  For a Woman named mary,
mary.method() will refer to a method in the intersection if a method
with the right name is available there; if not, it will refer to a
method in either Human or Female.  As there is no hierarchic
relationship between Human and Female, when both the classes Human and
Female define a method and the intersection does not resolve the
conflict, neither version of the method is predominant, and it is an
error to try to use it.

More generally, for a composite class that combines more than two
classes in which a same method name occurs several times, the
predominant method, if it exists, is the unique method that occurs in
the intersection where all the classes and all the intersections that
define that name overlap.  Or to put it differently: specialization is
a partial order among methods; if there is one version that
specializes all the others, that one is the predominant method.  If
not, there is none.

Now let us reuse the class Female:

class Bovine {
   def look_at_passing_trains() { ... }
}

class Cow = Female Bovine

For a Cow, only the part of Female outside of the intersection with
Human is relevant: the intersection with Human is simply ignored
in composing this new class.  The intersection of Bovine and Female on
the other hand is still empty, so let us now add a method moo():

class Bovine {
   def look_at_passing_trains() { ... }
   class Female {
      def moo() { ... }
   }
}

The method moo() characterizes Bovine rather than Female -- I
personally associate the concept of mooing with bovines rather than
with femininity -- and that is why I add moo() to the definition
of Bovine, further nested in Female, rather than the other way around.
In section II we will see that the order of nesting, which is
irrelevant for determining the predominant method, is however relevant
for scoping, so in quite some cases the order of nesting will
probably rather be chosen with scoping in mind.

The class Female was reopened for this example: classes can be
reopened as often as needed, as an outer nesting as well as nested in
others.  On the other hand, I did add the new code to the Bovine-block
I wrote earlier, instead of reopening Bovine, but only because the
two methods seem to go well together thematically.

When one arranges methods thematically by reopening classes several
times, tools can easily be provided, for instance as editor plugins,
to view the code by complete classes, or to see all variables in a
certain scope together, when this helps understanding.  Tools for the
opposite direction would be much harder to conceive.


II  Scoping

The order of nesting of classes is irrelevant to the identity of
predominant methods; but it remains the foundation of scoping, and so
determines the internal structure of composite classes.

Methods in an intersection can access only variables from classes that
make up the intersection.  There are no free variables that get caught
at composition time: the order of nesting around a method definition
determines locally which variable an identifier refers to in that
method.  The same goes for method lookup, when methods are called from
the active object -- that is, as a function call, as in "method()",
not by sending a message, as in "mary.method()".

as Female {
   var children
}

as Bovine {
   as Female {
      def moo() { if (children > 3) ... }
   }
}

(To highlight the semantic difference between class blocks in Tingle
and classes in more traditional languages, a block can also be
opened with the perhaps more suggestive keyword "as".)

In the body of moo(), the inner scope is formed by the intersection of
Female and Bovine; the next scope by Female only; and the next by
Bovine only.  The variable children in the class Female is not in the
same definition, but it is in scope.  (Here code browser plugins
for the editor as mentioned before would be useful.)  If the variable
children had not been declared in Female, but in Human, moo() would
not work, not even for the godess Isis:

class Isis = Female Bovine Human

The method moo() does not see the variable children in the class
Human, although it is present in the composite class Isis.

Using the notation class::variable the programmer can explicitly
override the scoping order, but not refer to a class outside of the
scope.  In moo() the distinction between Female::children and
Bovine::children could be made, but trying to use Human::children
would be an error, as Human is not in scope.  To explicitly refer to a
variable or method in an intersection, use constructions like
Female::Bovine::moo().  (Order is unimportant.)

In our moo(), super.moo() will refer to Female::moo() if that exists;
if not, Bovine::moo() is tried.  Super calls are function calls,
not messages.

For methods defined only two levels deep, I think everyone except the
most quarrelsome will accept this scoping rule as the obvious and
"correct" one.  The complete rule as currently used in Tingle however
is only one of the possible generalizations of the obvious one for two
levels.  It is rather complex: it was chosen because it assures that
intersections always have precedence over their separate classes, and
also that inner scopes have precedence over outer ones.

as A {
   as B {
      ...
         as Y {
            as Z {
               def myMethod() { print(v) }
  ...
}

The variable v will first be looked for in the intersection of all
classes A through Z (where it would reside if it was declared
immediately above myMethod in the source code), then in the smaller
intersections B through Z, C through Z etc.  When the intersection Z
through Z is reached, i.e. the class Z alone, and nothing has been
found, then we continue with the intersections A through Y until only
Y, then the intersections A through X until only X etc.

This way all continuous intersections (like C+D+E and P+Q+R+S+T) will
be searched; intersections are always searched before the single
classes that make them up, and also before intersections of only some
of the same classes; and inner classes before outer ones.

If you want to use a variable or method that is in a non-continuous
intersection, you can use the ::-notation; or change the order of
nesting for the current method.  The latter solution is possible
because, as classes can be reopened as often as desired, each method
can have its own nesting order; then again, nested blocks provide
visible structure to the program, so they should not be chopped up too
much.  The choice is a matter of taste.

This might also be the right moment to remark that when a program
makes you think hard about scoping, it might benefit from more diverse
and more descriptive identifiers.  The scoping rule is primarily
designed to make sure that data and methods that you will want to use
will very probably be in scope, and that more specialized versions
will always be "closer" in scope; not to settle accidental name
clashes.


III  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 bus {
   as car {
      def bus_stop() { ... stop() ... start() ... }
   }
   // nothing here -> equal to classical single inheritance
}

class Car = car
class Bus = bus car

The names "car" and "bus" versus "Car" and "Bus" will be used
throughout the comparison with single inheritance.  Better Tingle
style names would for instance be "vehicle" for "car" and "public" for
"bus", so that:

class Car = vehicle
class Bus = public vehicle

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

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

as busGUIObject {
   as bus {
      as carGUIObject {
         as car {
            def draw() { ... super.draw() ... }
         }
      }
   }
}

class CarGUIObject = car carGUIObject
class BusGUIObject = bus car busGUIObject carGUIObject

There is a better way, with shallow scoping, and all draw() methods
snugly together in the source code:

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

class CarGUIObject = GUIdraw Car
class BusGUIObject = GUIdraw Bus

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 bus {
         def costs() { ... }
      }
   }
   as employee {
      ...
   }
}

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

class CompanyCar = Accounting GUIdraw Car
class CompanyBus = Accounting GUIdraw Bus

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

We conclude this section with a rather more superficial example
of programming style: a more or less satisfying attempt to write down
what in a traditional object oriented language would be subclasses
that are smaller than their superclass:

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

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

class Ellipse = generic ellipse
class Circle = circular ellipse


IV  Mixins

Note again the contrast between the rule from section I to determine
the predominant method in a composite class, and the scoping rule from
section II.  The first is for when an object is accessed "from the
outside", with "object.methodname()"; the second is for accesses of
methods or variables inside an object, with "methodname()" or
"variablename" only.  ("Object.variablename" is not allowed in Tingle;
if it were, it would be the "predominant variable".)

Two distinct rules are needed because order of nesting is only
relevant for separate methods; when classes are composed, there is no
general order or nesting.

"Virtual" function calls, to abuse C++ terminology, as well as mixin-
like behaviour can be obtained by "stepping out of the scope" by using
"self.methodname()" instead of "methodname()".  Note that this
"virtuality" here is a property of the call, not of the method itself.
(Future versions of Tingle may introduce an annotation to methods
that forces all calls to them to this behaviour.)

In Tingle there is no technical difference between mixins and classes,
but the name mixin would typically fit a class without any
intersections, which accesses other methods of the same object through
"self" so that it can be mixed with any class that provides the
necessary methods.  Therefore we recognize the following code snippet
as a mixin.  It can be mixed with other classes that define isEqual
and isLess:

as Ordered {
   def isGreater(x) { ! (self.isEqual(x) || self.isLess(x)) }
   def isUnequal(x) { ! self.isEqual(x) }
}

Without nesting, the source code of this mixin does not show what
classes it should or should not be mixed with, nor what kind of object
"self" will then refer to.  As a matter of fact, in the dynamically
typed language Tingle, you can mix Ordered with classes that do not
provide isEqual and isLess, and so compose broken code.  For a very
general mixin like this one, all this is not a real problem; but often
templates with clean scoping are preferable above mixins.


V  Templates

These are two independent classes, without any intersections:

as Tapereader {
   def read() { ... }
}

as Diskreader {
   def read() { ... }
}

And this is how we extend them together:

as Buffered {
   as Tapereader, Diskreader {
      def read() { ... super.read() ... }
   }
}

class BufferedTapereader = Buffered Tapereader
class BufferedDiskreader = Buffered Diskreader

Which is short for:

as Buffered {
   as Tapereader {
      def read() { ... super.read() ... }
   }
   as Diskreader {
      def read() { ... super.read() ... }
   }
}

Syntax as used in the examples until here does however not allow to
express the following as a template:

as Buffered {
   as Diskreader {
      as Floppy {
         def read() { ... super.read() ... }
      }
   }
   as Tapereader {
      def read() { ... super.read() ... }
   }
}

So we add an alternative syntax for nestings that only consist of an
intersection:

as Buffered {
   as Diskreader->Floppy {
      def read() { ... super.read() ... }
   }
   as Tapereader {
      def read() { ... super.read() ... }
   }
}

And then we can write the template:

as Buffered {
   as Tapereader, Diskreader->Floppy {
      def read() { ... super.read() ... }
   }
}

As an aside, notice how the arrow syntax is also fit for expressing
traditional subclassing concisely:

class car->bus {
   ...
}

class Bus = bus car

This still differs from traditional object oriented languages, as the
extra part that is added to the base class is still an independent
entity and still gets its own name (bus versus Bus).  Still, in a less
dynamic version of Tingle, syntactic sugar could easily be added for
traditional subclassing, and the name of the extra part could be
filled in with a generated unique identifier.

Whether every situation will be served well by either mixins or the
simple templates described in this section, practice must show.  When
both are possible, templates seem the cleanest choice.


VI  Name spaces

Although the section on programming style illustrates that nesting of
scopes should not become as deep as a classical subclassing programmer
would expect, and complexity is thus kept in check, the language as
presented up to here does not scale well.  This section and the next
present two basic features that combine to support programming in the
large.  The first is simply name spaces.

Name spaces can be useful in all kinds of programming languages, but
they are especially so in Tingle, where they prevent that classes are
reopened without the programmer realizing that they already existed.
This is very useful for base classes with very generic names, like
indeed the class "generic" from the circles and ellipses example; and
in particular if they occur in other people's code.

Name spaces make it safe to use composed classes from libraries
without detailed knowledge of the entire library.  If the library
contains base classes with the same names as some of your own classes,
they will be distinct by virtue of their name space; so there is no
worry about name clashes with the base classes of the composite
classes you are using.

Name spaces are easy to use correctly: pieces of code that were not
designed together should always be in separate name spaces.  All class
names in a Tingle program are implicitly qualified with the current
name space, except if a name space is explicitly specified; so within
the default name space "main", any reference to a class named myWidget
will be interpreted as myWidget@main:

namespace main

as myWidget {
   ...
}
class MyWidget = Widget@WidgetLibrary myWidget

Modules with import en export lists would of course be better than
simple name spaces, but the extra advantages of modules over name
spaces would be the same as the extra advantages they bring to other
languages, so we focus on name spaces here.  Using name spaces we can
compose classes while keeping their internals completely apart for
scoping.


VII  Your Composite Class is my Base Class

Name spaces keep classes apart so thoroughly, that myWidget in the
last example could only be a mixin: it cannot access any of the
functionality of Widget@WidgetLibrary through scoping, only through
self calls.  (Assuming that we do not explicitly break through the
name space barrier by nesting with fully qualified base class names of
the form baseClass@WidgetLibrary, which would require detailed
knowledge of the library to do correctly.)

When name spaces have cut off all direct access to members of base
classes through scoping, all that remains visible is the set of
predominant methods of the composed class as a whole.  Such sets make
up the manageable API of the library.  The next step is that we make
this API available for scoping again, so that we can for instance
emulate subclassing from a composed library class.

To enumerate the drawbacks of using only name spaces: we can now use
Widget@WidgetLibrary as a black box, which exports its predominant
methods as an API, but when using this safe API in the mixin myWidget:

- we have to write "self." all the time;
- we lose coupling: the source code of the mixin does not specify
  which one specific other class it is meant as a mixin for
  (when it is in fact a hack for a subclass);
- most importantly, when the mixin and the old class provide methods
  with the same name, no new predominant method can be added to
  resolve the conflict, as the classes do not intersect
  (an important issue if what you actually wanted was a subclass).

Therefore we introduce the possibility to regard composite classes as
base classes when used outside of their own native name space, and
so use them for nesting:

as Widget@WidgetLibrary {
   as myWidget {
      ...
   }
}

class MyWidget = Widget@WidgetLibrary myWidget

(To be excruciatingly precise, Widget would actually be allowed to be
in the current name space, but not Widget's base classes.)

In this example, for the scoping rule, the composite class
Widget@WidgetLibrary will be considered as a base class without data
members, and with only the predominant methods of the composite.  This
corresponds with the viewpoint from a mixin, except that:

- methods of Widget can be called without "self.";
- coupling is explicit: if Widget is not part of a new composite
  class, then the intersection with myWidget will not be either, and
  no broken code will be present in the system;
- the methods of Widget are less dominant than those in the new
  intersection with myWidget;
- one can also refer to Widget using "super." and the ::-notation to
  directly access its predominant methods.

Composite classes are self-contained for scoping: if they are nested
themselves, their methods do not suddenly get the enclosing nestings
in scope, as they were not defined in them.  (Note that composite
classes imported from the same name space can share data; but this is
through scoping elsewhere, in the definition of their base classes; so
this is the exclusive responsibility of the library writer.)

In conclusion, Tingle does not only support a novel very open style in
code that belongs and is designed together, but also provides extra
hiding and encapsulation when we cross the boundaries of such modules; 
to fall back to what is essentially multiple inheritance with
sharing of common parts.


VIII  Access control

The meaning of such concepts as public, private and protected
members can be adapted from languages in the C++ tradition: in Tingle
private means "not accessible through scoping" and protected means
"not accessible through message sends", which is pretty much what they
mean in C++ too.  Note that private did not have to imply protected
in Tingle; but it seemed to make sense to make this so anyway.

Scoped private annotations as in Scala would seem like a perfect fit
for Tingle, but they are not yet implemented.  In particular "private
to the current nesting and everything lexically nested in it" seems
promising (although it could use a snappier name).

By default all data is protected, i.e. unreachable from outside of the
object; and all methods are public.  This can be modified using:

private var ...
protected def ...
private def ...

The exact meaning of private is: not accessible through scoping 
from outside of the current base class or the current intersection of
base classes.

The exact meaning of protected, also as implied by private, is: not
accessible for message sends, and also hiding all methods dominated by
this one when the predominant method is to be determined.

There is a special form of "protected" that is ignored for direct
self sends:

semiprotected def ...  // or shorter: semi def ...

Direct self sends are expressions of the syntax "self.msg(...)", as
opposed to sends to other expressions that happen to evaluate to be
identical to the sending object; and also function calls into composed
classes from other name spaces as described in section VII, which are
self sends by another name.


IX  Companion objects

The Tingle interpreter allows variable and method declarations at top
level, outside of any nesting.  These belong to a singleton object
named "*lobby*".  The class of the object "*lobby*" has no name and is
part of no name space.  You cannot send messages to the "*lobby*"
object (its name is not even a valid identifier).  But the members of
"*lobby*" are always in scope: the outermost scope, just before the
primitive functions.  Such top level entities are great for quick
experiments at the prompt, but not meant to hold global entities in a
program.

The clean alternative is companion objects.  Each composed class has a
companion object associated with it, an object with the same name as
the class, which is available in the same outermost scope as top level
declarations.  Companion objects however reside in the same name space
as their associated class, as opposed to top level declarations and
primitive functions, that reside in all name spaces at once.

Companion objects are explicit containers for methods and values that
belong with a class, but not with a specific instance of it; more or
less like class methods and class variables in some languages.  We
need only one extra keyword to declare them:

as bankAccount {
   var amountOfMoney
}

in bankAccount {
   var amountOfAccounts
}

"In" blocks can be nested in other "in" blocks in just the same way as
"as" blocks can be nested in other "as" blocks; and as a convenience,
they can be nested together in "at" blocks.  ("At" blocks can only
be nested in other "at" blocks, and can only contain further nestings,
no declarations of their own.)  Let us say we compose:

class BankAccount = bankAccount

Then not only is the class BankAccount created out the "as" part of
bankAccount; but a second, nameless class (refered to as ~BankAccount
in error messages) is created out of the "in" part, and then
a singleton object of that latter class is created under the name
BankAccount.  So there will now be one instance of the
variable "amountOfAccounts", part of the object BankAccount; and more
in general only one per composed class that includes the base class
bankAccount.

Companion objects have privileged access to the instances of their
associated class; and instances have privileged access to the
companion object of their class.  Message sends between them are
considered equivalent to direct self sends, making semi-protected
methods of the companion object accessible to instances, and
semi-protected methods of instances accessible to companion objects.
The latter property makes companion objects fit as factories:

as counter {
   var value  { get, semi set }  // generates getter and setter
   def step() { value = value + 1 }
}

in counter {
   def make(|object) {           // object is a local variable
      object = new;              // create object of associated class
      object.set(0);             // use semi-protected setter
      object                     // return object
   }
}

class Counter = counter

The factory method is very wordy, so that lots of comments could be
added.  This is how you would normally write it:

in counter {
   def make() (new).set(0)
}

You make a new Counter using "Counter.make()".  Companion objects
can be refered to by their fully qualified name, of the form
"Counter@main", or (usually) without the name space component.  In the
latter case, variables in scope with the same name can have
precedence; but the convention of giving composed classes names that
start with a capital letter makes this a non-issue.


X  Vision in Three Paragraphs

In class based languages, class names are usually nouns.  The system
described above invites the programmer to write many classes as
adjectives, or concerns; from these, composite classes are
constructed, the names of which will again often be nouns.

The second step, composition, makes that we need a second mechanism
next to scoping to determine which methods are predominant in the
composite class; in traditional class based languages these mechanisms
can be one and the same, because there is a hierarchy among
superclasses and subclasses.

I hope that writing concerns as classes can contribute to untangling
code conceptually, albeit at the price of more complex scoping
mechanics.  A good development environment should help the programmer
cope with the latter in a way that it cannot do for the former.


Dirk van Deun, 2007-2010 (dirk at dinf.vub.ac.be)
