2005-08-22

Celebes Kalossi: Who knows best?

This post expands and enlarges on my previous post OOP Without Inheritance.

Celebes Kalossi (CK) is the name of a model of object-oriented programming I'm developing. It's also the name of a (currently hypothetical) programming language that implements it. Here I'm going to talk about how objects are constructed in CK. There will be later posts that discuss various other features. I'll say right off that CK is class-based (loosely speaking) rather than delegation-based like Javascript or Self.

Mainstream OO programming languages like Smalltalk, Java, C++, and C# natively support a Daughter Knows Best (DKB) model: a method in a subclass overrides the correspondingly named method in a superclass. All except Smalltalk also require that the static types of the arguments match in order for an override to be successful. I say Daughter Knows Best because the overridden method need not be invoked at all unless the overriding method decides to do so.

Simula and Beta, per contra, use a Father Knows Best (FKB) model: the superclass method is invoked first and foremost. In fact, subclass methods are not invoked at all unless the superclass method decides to do so. This is perfectly symmetrical with the "Daughter Knows Best" model.

There are advantages and disadvantages to both models. In DKB, the subclass can take complete control, which is sensible because it understands its own needs better than the superclass. However, when a superclass method invokes a subclass method under the guise of invoking its own method (because the self/this object belongs to the subclass), it has no guarantees that the subclass method will Do The Right Thing. In FKB, the subclass is stuck with the behavior that the superclass imposes, for good and bad: good, because the superclass can prevent the subclass from going off the rails; bad, because the superclass may do things the subclass does not want.

In CK, all classes are equal, and specify exactly which behavior is appropriate for them. This is achieved by partitioning the notion of class into two notions: type and behavior. To create an object, you specify its type: in statically typed Celebes Kalossi, variables specify their types. The methods you can invoke on a type are the methods it declares public, plus the methods declared public by all its supertypes. A type can have more than one supertype. Consequently, a type is like an interface in Java or C#, except that types can have instances, whereas interfaces can't.

A behavior is simply a named set of methods plus instance variables. Objects cannot be created nor can variables be typed using the name of a behavior. The instance variables are always private to the behavior alone, so if you want to make them accessible to the outside world, you must provide getter and/or setter methods for them within the behavior. The methods, however, can be specified as private, external, or standard (no keyword in the syntax). Private methods, like instance variables, are defined by the behavior and visible only within the behavior. External methods can only be declared, not defined, and indicate which methods this behavior depends on but does not itself define. Finally, standard methods are defined by the behavior and are available in types that use the behavior.

What do I mean by "use the behavior"? In the declaration of a type, the programmer can specify that it has no behaviors, in which case it is an abstract type, or the programmer can specify one or more behaviors. The behaviors must fit together like jigsaw puzzle pieces: if a method is declared as external in one or more behaviors of the type, it must be defined with a standard definition in exactly one behavior of the type. Furthermore, each of the public methods of the type (including the public methods of its supertypes) must correspond to a standard method defined in a single behavior. However, behaviors are never inherited from supertypes: a type that wishes to have the same behavior as a supertype must specify that behavior explicitly.

The model also provides control of the visibility of individual method names. When a type declares that it uses a behavior B, it may mention standard methods to be suppressed or renamed. A suppressed method is not visible to the jigsaw-puzzle mechanism described above; this allows creating types whose behaviors have method names that accidentally clash. Renaming a method allows it to be invoked from other behaviors under a different name. This allows the DNB model to be simulated: the subtype uses its supertype's behavior, suppressing conflicting methods that are not going to be called, and renaming ones that are. Thus there is no mechanism in CK corresponding to super in DKB languages or inner in FKB languages.

There is some syntactic sugar that makes the model easier to use. In particular, a behavior A can declare that it uses another behavior B. This simply means that whenever a type uses behavior A, it also automatically uses behavior B; no special relationship between A and B is necessarily implied. Furthermore, types can define instance variables, private methods, and standard methods as well as declaring external methods. In terms of the model, these things are really declared in an anonymous behavior which the type automatically uses.

Behaviors are something between mixins and traits. Traits don't have instance variables, which in the traits model are defined within classes that incorporate the traits. Mixins have instance variables that are visible to all other mixins within the class, bringing in all the problems of unrestricted multiple inheritance. Behaviors share the orthogonality of traits: you can just combine them without worrying about the order in which they are to be combined, since identical instance variable names are irrelevant (instance variables being private), and identical method names are forbidden unless renamed or suppressed. Since each behavior carries its own state with it, and behaviors are plug-replaceable, one can create closely related types, or even equal types (loops in the subtype-supertype relationship are not forbidden) that implement variant behaviors.

No comments: