Symbolic Logic:Programming:Object Oriented Logic Programming

From Knowino
Jump to: navigation, search

Object Oriented Programming is an implementation of set theory. However many OO languages are compromised in there implementation of set theory, which has lead to problems with the use of inheritance. In particular imperative languages have some compromises that undermine the logical structure of set theory.

Part of this compromise is the requirement in common OO languages (C++, Java, Eiffel ...) that a call to a function signature should invoke a single function body. This is not a logical consequence of set theory. A function in an inheriting class overriding the implementation in the base class is a logical exception system.

An exception system is where I make a statement, but then qualify it with an exception. For example,

Exception systems play a role in the evolution of knowledge. But any statement, that is qualified by an exception cannot be relied on as always true in every situation. Exception qualification should only be used as "the exception", not as the bedrock of solid re-usable logic.

The exception system breaks down when there is multiple inheritance because it cannot resolve to a single function to call ( the diamond problem).

Problems with the implementation of multiple inheritance in imperative languages has lead to single inheritance languages (Java, C#, ...). This further compromises the logical underpinnings of OO in those languages.

The logical interpretation of inheritance as set theory indicates that multiple function bodies may be invoked by a single function call. But this is problematic because the order of execution is important in an imperative language. If two function bodies are to be called, what order should they be called in? In Logic Programming, processing order is irrelevant, so there is no problem.

Classes should each implement a single simple piece of functionality in a way that is indepent (decoupled) from other classes. Rich functionality is then composed from simple functionality. The order of inheritance is irrelevant in a logical implementation of inheritance.

Class inheritance models "is a" relationships. It is also possible to model "has a" relationships in a manner that extends inheritance. This allows the plugging together of class functionality in a truly component based model, without the addition of extra functionality in inherited classes to glue them together.

Contents

[edit] Classes and Set Theory

Class inheritance in object oriented programming may be thought of as an implementation of set theory where,

For example where there is a class Dog and a class Animal, in set theory there are corresponding sets Dogs and Animals. Dog inherits Animal then means that the set Dogs is a subset of the set Animals.

For example take the rule, "all dogs chase cats". In set theory this may be written,

d \in Dogs \and c \in Cats \and d.See(c) \implies d.Chase(c) \!

Which may be written as a class method,

    class Dog
    {
        void See(Cat cat)
        {
            Chase(cat);
        }
    }

However there is a problem. What happens when there is a class CatLovingDog that inherits from Dog. A "cat loving dog" would greet a cat, not chase it. To allow for this the logic would be changed to,

CatLovingDogs \subset Dogs \!
d \in Dogs \and c \in Cats \and d.See(c) \and \neg d.ChaseException() \implies d.Chase(c) \!
d.ChaseException() = (d \in CatLovingDog) \!
d \in CatLovingDog \and c \in Cats \and d.See(c) \implies d.Greet(c) \!

In Object Oriented programming this would be written as,

    class CatLovingDog
    {
        void See(Cat cat)
        {
            Greet(cat);
        }
    }

In effect overriding virtual functions in an object oriented language creates a exception condition system represented here by the d.ChaseException() condition. There is no way to turn this exception system off.

An exception system is where I make a statement, but but qualify it with an exception. In our example, we make the statement,

"all dogs chase cats"

but I qualify it with the exception,

"except cat loving dogs"

Statements that are qualified by exceptions cannot be relied on, and should not be used as a logical basis for functionality. Instead exceptions should be used sparingly, in non key areas. Statements qualified by exceptions are default statements that are usually true, not always true.

Suppose I have a class CatHatingDog. Clearly "cat hating dogs" chase cats. So the rule is,

CatHatingDog \subset Dog \!
d \in CatHatingDog \and c \in Cat \and d.See(c) \implies d.Attack(c) \!

by combining this with the rule for Dogs,

d \in Dogs \and c \in Cats \and d.See(c) \and \neg d.ChaseException() \implies d.Chase(c) \!

we get,

d \in Dogs \and c \in Cats \and d.See(c) \and \neg d.ChaseException() \implies d.Chase(c) \and d.Attack(c) \!
    class CatHatingDog : Dog
    {
        void See(Cat cat)
        {
            super.See(cat);
            Attack(cat);
        }
    }

CatHatingDogs both chase and attack cats. But to to get back what we already new about Dogs, we need to add the line super.See(), which may be expanded as Chase(cat).

Note that mutliple statements separated by ";" mean the (and) conjunction of the two statements.

[edit] The Diamond Problem

The exception system implicit in the overriding of virtual functions is designed to give a single function body to execute when a function is called. The Diamond Problem indicates a case where it fails to do this. If a class inherits D inherits from B and C which inherit from A, forming a diamond shaped inheritance diagram. A method signature defined in A, but overridden in B and C becomes ambiguous in D.

The problem arises because inheritance, as implemented in imperative languages, cannot except that a call may invoke multiple function bodies.

As functions are implementations of rules in logic this is rather strange. Suppose we have a set of objects that beep when poked. Also we have a set of objects that blink when poked. The objects in the intersection of the blinkers and beepers must both blink and beep when poked. In logic,

x \in Blinkers \and x.Poke() \implies x.Beep() \!
y \in Beepers \and y.Poke() \implies y.Blink() \!
BlinkBeepers = Blinkers \cap Beepers \!
z \in BlinkBeepers \and z.Poke() \implies z.Beep() \and z.Blink() \!

Using classes,

    BlinkBeeper inherits Blinker
    BlinkBeeper inherits Beeper
    class Blinker
    {
        void Poke()
        {
            Blink();
        }
    }
    class Beeper
    {
        void Poke()
        {
            Beep();
        }
    }

Then from the logic, this the above class declaration is equivalent to,

    class BlinkBeeper
    {
        void Poke()
        {
            Blink();
            Beep();
        }
    }

Current OO languages dont allow a function call to invoke two bodies because there is no defined order to call them in. In logic this is not a problem. In an imperative programming language this is a big problem. But imperative programming languages are essentially illogical.

The Diamond Problem has lead to the removal of multiple inheritance in successor languages to C++ like Java and C#. But in reality multiple inheritance is as natural as an object being a member of multiple sets.

[edit] The Return Type

The other criticism levelled at multiple inheritance is that it makes the interface brittle because the return type cannot be changed in an overridden function. Clearly in a Logic Programming paradigm, the return value is regarded as being equivalent to another parameter. So the return type should be part of the signature.

[edit] Too Closely Coupled

Some people say that class inheritance leads to close coupling between the inheriting and the inherited class. That is, the developer needs to know how the inherited class works to use inheritance properly. I think this is a perception because of the structural problems with inheritance as implemented in current imperative languages.

If inheritance is simply an implementation of the subset relation I find it hard to believe that there can be serious problems with it. It is a simple subset relationship.

Inheritance that is used without there being a genuine "is a" relationship is clearly incorrect because it goes against the underlying logic.

[edit] Sets and Logic

There are simple laws that relate set theory to logic,

x \in A \cap B \iff x \in A \and x \in B \!
x \in A \cup B \iff x \in A \or x \in B \!
A \subset B \and x \in A \implies x \in B \!

The logical system created by applying these laws to set conditions is readily solvable using Value Set Programming. By identifying types as sets,

A \  inherits \  B \iff A \subset B \!

But what about member variables?

[edit] Member Variables

Member variables are an implementation strategy for recording facts about objects. A member variable allocates memory to store the value with the object. A fact,

 x.GetName() = "Bob"

This kind of fact is called an attribute. It is implemented using a member variable,

 x.name = "Bob"

Without member variables the fact would need to be recorded in a (hash) table of facts.

If class A is a subset of class B, all attributes of B are attributes of A. So memory needs to be allocated for member variables to record the attributes.

[edit] Conclusion

All of inheritance may be implemented using simple logic. Member variables are an implementation strategy for efficiently recording facts in memory.

In Meta Programming we will see how this logic may be unravelled at compile time to give an efficient implementation.

The language structures of Object Oriented programming are useful to make programming more understandable to the programmer.

[edit] Set Theory, Interfaces and Callbacks

Class inheritance gives the ability for the inherited class to call a function on the inheriting class, without knowing the inheriting class. For example,

    class Vault
    {
    protected:
        abstract void Alarm();
 
    public:
        void Open()
        {
            if (not Authorised())
            {
                Alarm();
            }
        }
    }
 
    class Bank : Vault
    {
        void Alarm()
        {
            logger.Write("The vault has been opened without authorization.");
            ...
        }
    }

The Vault class is able to call the Alarm method without the implementation of the Alarm method, or the class to which the Alarm method belongs.

Banks \subset Vaults \!
v \in Vaults \and v.Open() \and \neg v.Authorised() \implies v.Alarm()) \!
b \in Banks \and b.Alarm() \implies b.logger.Write(message) \!

There is an element of dishonesty here. A bank is not a vault. A bank has a vault, and may have a number of vaults. Maybe the bank has a main vault and a secondary vault. This can be repres

Bank.HasA(Vault, main) \!
Bank.HasA(Vault, secondary) \!
b \in Banks \and b.AlarmMain() \implies b.logger.Write(...) \!
b \in Banks \and b.AlarmSecondary() \implies b.logger.Write(...) \!

When the main vault alarm goes we would like the AlarmMain method to be called on the bank. Similarly for the secondary vault alarm.

Now we need some linking logic to link up the vault alarm method to the relevant bank alarm method.

Bank.HasA(Vault, Main) \and b \in Banks \implies b.Main.Alarm() \iff b.AlarmMain() \!
Bank.HasA(Vault, Secondary) \and b \in Banks \implies b.Secondary.Alarm() \iff b.AlarmSecondary() \!

These rules may be generalised as,

Bank.HasA(Vault, x) \and b \in Banks \implies b.x.Alarm() \iff b.x.CallbackAlarm()() \!

where,

b.Main.CallbackAlarm() = AlarmMain
b.Secondary.CallbackAlarm() = AlarmSecondary

[edit] Standard Features

[edit] Naming

A naming system allows us to name language elements,

The naming system allows us define a naming path for language elements. For example,

animal.mammal.dog

Language also provides naming scope, which means that you can reference language elements without referencing the full name. The scope is a region of the program in which a name prefix is defined.

So if I define a namespace,

namespace animal.mammal
{
dog refers to animal.mammal.dog
}

[edit] Variables

A variable is a reference to an object.

[edit] Functions

Functions define mappings between objects (referenced by parameter variables).

A function is identified by its signature. The signature consists of,

Each function has an associated pre-condition which determines if a call is invoked. A call may invoke multiple functions.

[edit] Return Type in the Signature

Note that many languages do not treat the return type as part of the signature. From a structural point of view a return type is similar to a parameter. If we write,

n = Factorial(6)

or

Factorial(n, 6)

the meaning (with a suitable overloading of the Factorial function) would be identical.

There are situations where overloading by return type would be useful.

[edit] Object

Every object has two primary attributes,

Identity means that two objects can be distinguished, and two references refering to the same object will be equal.

Also we can ask what class the object belongs to. Class defines membership of a set.

[edit] Project

A project is a grouping of classes that work together to offer services to the outside world.

[edit] Class

A class is a set of objects. Every Class has attributes,

[edit] Abstraction and Encapsulation

Abstraction and Encapsulation is the hiding of language elements so that they may only be accessed from certain scopes. The language defines the rules which control the accessibility of objects, using keywords such as,

Along with the control of reference object oriented programming uses a different syntax for calling functions that are defined within a classes scope,

    name.Equals("Roger")

is used instead of,

    String.Equals(name, "Roger")

This as purely a syntactical difference, of no fundamental significance. Both these calls should be legal and mean exactly the same thing.

Of course syntactical issues are important. The human brain is better at reading syntax which is varied and gives hints as to the usage. A language like LISP that is all brackets may be fine for the computer but counting brackets and understanding complex expressions may be difficult for the human reader.

[edit] Inheritance

Inheritance defines a set membership relationship between the objects that belong to classes. If class A inherits from class B then an object from class A also belongs to class B.

Class is like a compile time version of set theory. The following table describes the correspondence.

Classification of Symbolic Logic Systems
Classes Sets Maths
A inherits from B A is a subset of B A \subset B
Declaring - class A a = new A() a is a member of A a \in A
A inherits from B and class A a = new A() a is a member of A and B a \in A \and a \in B

[edit] Polymorphism

Polymorphism refers to the conditions under which a function is called. There is logically a pre-condition associated with each function call. The pre-condition includes,

[edit] Overriding Functions

[edit] Virtual

A virtual function is a function that has an extra pre-condition. The pre-condition is that the method signature is not also defined in an inheriting class that the object belongs to.

If a class Square inherits from ShortRectangle and ShortRectangle implements the virtual function Validate.

    Square inherits ShortRectangle
 
    virtual bool ShortRectangle.Validate()
    {
        return Width() < Height();
    }
 
    virtual bool Square.Validate()
    {
        return Width() = Height();
    }
 
    class Square a = new Square();

Logically everything true about the base class should be true about the base class should be true also in the inheriting class. But overriding a virtual function in a child class disables the implementation in the base class. Logically a virtual function has a pre-condition that the function is only applied if not overridden in the implemented class.

A virtual function is a default implementation of a function only. There is no guarantee implemented in the function will be used in a derived class. So anything definite that we want to say about a class should not be in a virtual function.

[edit] Non Virtual

The implementation of non virtual functions in most languages is illogical (e.g. C++). For a non virtual function the implementation may still be overridden in the base class. But now which method we get depends on the type of the variable. This is inconsistent with mathematical logic.

Logic says that any true fact about a base class should also be true in the inheriting class. For virtual functions we were able to get around this with the idea of a pre-condition on the method.

But for non virtual functions a call to the function must call the base class implementation and the child class implementation, in order to be logically consistent.

[edit] Overloading

Overloading is where the same function name may be defined multiple times for the same class. A function can only be defined once for its function signature.

It is possible for multiple functions to be invoked for the same function call. This is occurs when there is a "polymorphic change of type".

My view is that in this case both functions should be called, and the results combined. How the results are combined is described in the Renaming Inheritance section.

The use of overloading may make code very confusing. But overloading also has its uses and is logically equivalent to polymorphism.

[edit] Renaming Inheritance

Inheritance is related to set membership. If A inherits from B this means that A is a subset of B. An object of class A is also in class B.

The functions of class A can access the functions from class B. Also if class A overrides virtual functions the class B can gain access to the functions of class A. This two way relationship implements a relationship between the two classes.

But when a class has a member variable there is only a one relationship. This means that the member variable class functions have no way of calling functions on the base class.

[edit] Bank Vault Example

Suppose a bank has two bank vaults. The alarm should go off in the bank if either vault is opened without authorization.

The Vault class calls the Alarm() method if the vault is opened without authorisation.

class Vault
{
protected:
abstract void Alarm() rename as Alarm%; // Alarm% is the template for the name in the inheriting class.
private:
void Open()
{
if (not Authorised)
{
Alarm();
}
}
}

The Bank class has two vaults called Main and Secondary. It needs to respond to the Alarm raised from either Vault.

class Bank
{
inherit Vault as VaultMain
inherit Vault as VaultSecondary
// In heriting from Vault as VaultMain Bank, inherits the Alarm method which is renamed.
// The rename template Alarm% is used with % substituted with VaultMain to give the inherited name.
void AlarmVaultMain()
{
Logger.Write("Alarm on main vault");
Alarm();
}
// The rename template Alarm% is used with % substituted with VaultSecondary to give the inherited name.
void AlarmVaultSecondary()
{
Logger.Write("Alarm on secondary vault");
Alarm();
}
}

[edit] Details

The "rename as" keywords indicate that the function will be inherited using renaming inheritance.

The template provided after "rename as" is used to construct the name of the function in the inheriting class. If there is a % in the template it is replaced by the inherited name.

In the above example the "rename as" template of the Alarm function is Alarm%. So when Vault is inherited as VaultMain the name of the Alarm function is AlarmVaultMain in the bank class.

If there was no % in the name the new name would be just the template. For example if there was a function,

abstract void GeneralAlarm() rename as GeneralAlarm;

then the Bank class would inherit the one GeneralAlarm method.

The declaration,

inherit Vault as VaultMain

differs from a member variable in that there is no member variable to access. Only the renamed functions provide access the inherited class.

[edit] Shared Signature

If the % is ommitted from the renaming template then if the base class inherits the same class multiple times the same signature may be implemented multiple times. This is called signature sharing. In fact signature sharing may occur without using renaming inheritance.

Logically in this case all the implementations should be inherited. A call to a function should invoke all functions that match the signature.

The logical interpretation is that all these separate implementations should be consistent. They should return the same result given the same inputs.

But it is helpfull to provide another interpretation of this. A combination operator may be provided that combines the results together. For a function that returns a boolean this operator would be "and" by default.

Shared inheritance allows code the traversal of an object structure. For example it allows ever attribute of an object to be visited for purposes such as validation or saving data to a database.

[edit] Alternatives

Here are some alternatives provided in different languages,

The reason renaming inheritance is suggested is that it allows localisation of implementation. Each class can deal locally with functionality required for itself, while communicating with an inheriting class if needed. The results of local calculations can then be automatically combined together to form a global result, through name sharing.

[edit] Closure

A Closure is a combination of Lambda Calculus and nested functions. Nested functions give access to the local variables from the enclosing function.

Lambda Calculus is a simple but powerfull idea that the formal parameter belongs in the code instead of the function name. This allows a degree of flexibility with the use of parameters that has staggering power and expressiveness. In fact the Lambda operator alone gives a language which is sufficient to write any program.

However the art of programming is essentially about writing programs that other people may have a chance of understanding. The role that Closure have for this purpose is to allow programmers to construct there own language control structures (for loops, if statements, ...). The classical example of this is the ForEach method on a list,

listBooks.ForEach(book : book.Read());

Here "book : book.Read()" acts like a function taking a parameter (the book), and calling the Read method on it. The utility of this is its accessibility and readability. If we had a static function Read that took a book as a parameter we could have written,

listBooks.ForEach(&Read);

The code is shorter but harder to understand. We have to look at the definition of Read to find out that it is a function that takes a parameter before we can guess at the intent of the code. This is why function pointers are avoided by most programmers.

The term Closure appears to refer to the access to local variables. However it is good practice for a loop structure to call only one function in its body. This allows the action performed repeatedly to be documented and described individually. Smaller functions with a single simple purpose are easier to understand.

Closures may be implemented by constructing functions functions for the closure code which has parameters for all the local variables used ( Lambda lifting).

[edit] Mappings

A mapping is a a data type (i.e class) that associates one set of objects with another set.

A mapping may be declared,

    X name[Y, Z ...];

where X is the image and Y, Z ... is the domain.

for example

    long myArray[long];
    String tagalogWord[String];
    Color image[long, long];

The range of values may be restricted using a range type.

    Color image[1..1920, 1..1080];

A mapping is a class and may be inherited. For example,

    class Vector(class T)
    {
        inherit T[long];
    }

all mappings implement the functions,

Header text Header text
X M.Lookup(Y, Z ..) x.Lookup(y, z ...) is the same as x[y, z ...].
bool M.ForEach(bool action()) Used to perform "action" on every object in the mapping.
bool M.Size() How many objects in the mapping image.

Also ever member function in the image class is implemented in the mapping. The implementation depends on the renaming.

For example,

     class ButterflyWidget
     {
         bool Draw(Image image) rename as Draw;
         String Name() rename as Name%;
     }

then Butterfly[1..10] has the methods,

     bool Draw() rename as Draw
     {
         result = ForEach(x | x.Draw());
     }
 
     String Name(1..10 index)
     {
         result = [index].Name();
     }

Functions whose name is shared my be used to traverse the objects in a mapping.

The Lookup and the ForEach functions are overidable.

If the domain classes implement a function called Hash returning a long it will be used in implementing a hash table.

There is no way of telling if an object has been added to the mapping yet, because this would not make sense in logic. The functions Size and ForEach both finalise the mapping so that no more objects may be added.

[edit] Defining Class Structure at Run Time

[edit] Dynamic Class Inheritance Statements

Class inheritance declarations like,

inherit A
inherit A as B

are unconditional and fixed at compile time. Class inheritance statements support also describe inheritance but they may be conditional.

Inheritance
Declaration Statement
inherit A IsA(A)
class B inherit A B.IsA(A)
inherit A as S HasA(A, S)
class B inherit A as S B.HasA(A, S)
X x any x; x.InClass(X)
x = new X x.InstanceOf(X)

These inheritance statements may be used freely in the scope of a class definition and within function definitions. These statements may be within if statements that make the inheritance conditional. The execution of these functions to evaluate the types of variables usually occurs in Inheritance resolution.

However it is possible for the inheritance to be undecided until the Execution Meta Phase. Where there is a dynamic inheritance statement which cannot be resolved as true or false in the Inheritance Resolution phase there must be support for inheritance which is activated in the execution meta phase. The truth of the inheritance statements becomes a pre-condition on inherited methods.

The fixed syntax for inheritance declarations is encouraged for readability over inheritance statements as they are more understandable.

[edit] Two Stage Call

Each call to a function is implemented as 2 stages. For each function implementation matching the name, and number of parameters (arity) there is a,

Every call to a function first calls a router function that matches the name and arity. For each implementation function matching the name and arity the router function does

    if (pre-condition)
    {
        Call function
    }

For example,

    class Animal
    {
    public:
        inherit Attribute(String) as Position;
        bool Move(Point p)
        {
            result = SetPosition(p);
        }
    }
    class Dog : Animal
    {
    public:
        bool Move(Point p)
        {
            RunTo(p);
            OnGround(p);
        }
    }
    class Bird : Animal
    {
    public:
        bool MoveTo(Point p)
        {
            FlyTo(p);
        }
    }
    class Fish : Animal
    {
    public:
        bool MoveTo(Point p)
        {
            SwimTo(p);
            InWater(p);
        }
    }

Then the router function for MoveTo would be,

    bool Animal.router.MoveTo(Object object, Object point)
    {
        if (object.IsA(Animal) and point.IsA(Point))
        {
            Animal.MoveTo(object, point);
        }
        if (object.IsA(Dog) and point.IsA(Point))
        {
            Dog.MoveTo(object, point);
        }
        if (object.IsA(Bird) and point.IsA(Point))
        {
            Bird.MoveTo(object, point);
        }
        if (object.IsA(Fish) and point.IsA(Point))
        {
            Fish.MoveTo(object, point);
        }
    }

A call to a function,

    Animal dog = new Dog;
    dog.MoveTo(new Point(5,6,0));

is equivalent to,

    Animal.router.MoveTo(dog, new Point(5,6,0));

when expanded out by Partial Evaluation the code becomes equivalent to,

    Animal dog = new Dog;
    Animal.MoveTo(dog, new Point(5,6,0));
    Dog.MoveTo(dog, new Point(5,6,0));

The generated code needs to be equivalent to the implementation described. Because of Partial Evaluation in a particular call context often the type will be known and the router function expanded. However if not the router function may make use of a VTable in the generated code.

[edit] Pre-conditions

Pre-conditions are implicitly defined by parameter types. But they may also be added explicitly. For example

    long Factorial(long n)
    {
        precondition
        {
            n == 0;
        }
        return 1;
    }
 
    long Factorial(long n)
    {
        precondition
        {
            n > 0;
        }
        return n * Factorial(n-1);
    }

is equivalent to,

    long router.Factorial(long n)
    {
        if (n == 0)
        {
            result = 1;
        }
        if (n > 0)
        {
            result = n * Factorial(n-1)
        }
    }

[edit] Characteristics and Pre-conditions

The characteristics input("in"), output ("out"), and unique may be used to create conditions that control the order of calculation.

The pre-conditions unique and multiple determine if Value Sets are needed for parameters,

For example,

    unique out long Random(unique in double seed, unique out double newSeed);

is a function that calculates its return value from its input value. The characteristic conditions may also be written,

    long Random(double seed, double newSeed)
    {
        characteristic
        {
            seed.Known();
            !newSeed.Known();
            !result.Known();
        }
        pre-condition
        {
            seed.Unique();
            newSeed.Unique();
            result.Unique();
        }
        ...
    }

Testing to see if a value is already calculated (Known) is not generally allowed in CLP. For this reason input and output characteristics are not part of the precondition. The characteristics are tested separately in the characteristic condition, to determine which of the logically equivalent implementations are ready to be run now.

When there are multiple implementations with the same name, arity and precondition, the characteristic conditions determine which function implementation is used, and when. It is up to the developer to insure all implementations with the same pre-conditions but different characteristics are logically equivalent. The characteristics only choose which implementation is used.

Characteristics may delay the execution of the implementation of a function. If none of the characteristic conditions are met the call is placed on a queue to be run later when the characteristic conditions are met.

Characteristics should be used sparingly. If providing multiple function implementation with the same pre-condition, but different characteristics, the developer must insure that each implementation is logically consistent.

Characteristics should only be used,

As a general principle it is better to leave out information that Meta Phase 1 will calculate.

This results in code that easier to re-use and modify.

Note: Characteristics are similar to modes in [Mercury].

[edit] Implementation of Inheritance

The architecture chosen to implement multiple inheritance differs considerably from the C++ implementation. The C++ implementation is not suitable for fine grained inheritance. By fine grained inheritance I mean a class composed of multiple sub classes, each using Localisation of Functionality.

The C++ architecture uses one VTable pointer for each inherited class that has virtual functions. Also there are extra pointers associated with the use of virtual inheritance. For logic, all inheritance is virtual, and all functions are virtual.

The architecture chosen is non-hierarchical. Each inherited class is represented by a structure within the instance, called the implementation, which contains the local variables. The instance has a single pointer to a static structure that stores the identity information of the class.

The identity object implements the QueryInterface which returns interfaces to the object. The interface is a structure, but is similar in effect to a VTable.

A pointer is represented by an ObjectPointer, which includes two pointers,

The purpose of the ObjectPointer is to remove the need for each inherited classes implementation to store its own VTable pointer. This makes the class instance smaller at the cost of longer pointers.

When a class is inherited without renaming, an ObjectPointer may be created from a pointer to the interface. When a class is inherited with renaming the interface pointer records which inherited class implementation is being pointed to.

[edit] Internal and External Names

When there is renaming inheritance there may be internal and external names for each function.

Otherwise the internal and the external names will be the same.

The internal name and the external name will be diferent if there is renaming, and the function external name depends on the name for the inherited class.

The external name is represented by the "rename as" clause, with the % replaced by the "as" name.

    inherit Attribute(String) as Name;
    String Get() rename as Get%;

[edit] Object classes

The Object classes are used in implementing the inheritance architecture.

[edit] Object Identity

The base class for identity.

    class ObjectIdentity
    {
    public:
        virtual void *QueryInterface(ObjectIdentity *identity) = 0;
        virtual void *IsA(ObjectIdentity *identity) = 0;
        virtual void *HasA(ObjectIdentity *identity) = 0;
    }

[edit] Object

Base class for all object instances. No virtual functions here.

    class Object
    {
    private:
        const ObjectIdentity *identity;
    public:
        Object(ObjectIdentity *p_identity) : identity(p_identity)
        {}
        template <class T>
        Interface::T* QueryInterface<T>()
        {
            return static_cast<Interface::T *>(identity->QueryInterface(Instance::T::Staticidentity));
        }
        template <class T>
        bool InstanceOf<T>()
        {
            return identity == Instance::T::Staticidentity;
        }
        template <class T>
        bool IsA<T>()
        {
            return identity->IsA(Instance::T::Staticidentity)
        }
        template <class T>
        bool HasA<T>()
        {
            return identity->HasA(Instance::T::Staticidentity)
        }
    }

[edit] Object Pointer

The ObjectPointer acts like a pointer but stores the interface.

    template <class T>
    class OP<T>
    {
    private:
        Interface::T *vtable;
    public:
        Object *pointer;
        OP(Instance::T *o) : pointer(o)
        {
            vtable = object->QueryInterface<T>();
        }
        template <class X>
        OP(Instance::X *p, Interface::T v) : pointer(static_cast<Object>(o)), vtable(v)
        {
        }
        template <class X>
        OP(const OP<X> &op) : object(op.pointer)
        {
            if (Instance::T::static_identity == Instance::X::static_identity)
            {
                vtable = static_cast<Interface::T>(op.vtable);
            }
            else
            {
                vtable = object->QueryInterface<T>();
            }
        }
        Interface::T* operator ->()
        {
            return vtable;
        }
        Implementation::T* GetImplementation()
        {   // Get the implementation (so you can access the member variables), using the object and the offset.
            return static_cast<Implementation::T>(static_cast<long>(object) + vtable->offset);
        }
        ...
    }

[edit] Calling Functions

Logic C++
    MyInterface myInterface = new MyClass;
    myInterface->MyFunction(p)
    OP<MyInterface> myInterface = new MyClass;
    myInterface->MyFunction(myInterface, p);

The -> operator of the Operator Pointer returns a table of pointers to static functions. The Operator Pointer needs to be passed to the static function as the first parameter.

[edit] Classes for a Logical Class

To implement the required architecture, multiple physical C++ classes are needed to implement a single logical class. The classes are,

Classification Example Description
Implementation MyClass Implements the functions and member variables specific to the class.
Instance Instance::MyClass The collection of sub classes that represent the complete implementation of the class.
Interface Interface::MyClass Allow function to be called from other projects without static linking.
Identity Identity::MyClass Record information describing the identity of the class. There is a single static instance

[edit] Implementation

The implementation class has all the member variables and functions implemented in the class. But is not instantiated directly. It represents the class, minus the inheritance, with the instance class creating the inheritance structure.

All the functions are static with the Object Pointer passed as the first parameter. The implementation class is obtained from the Object Pointer.

The implementation class may only access its own member variables, so all member variables must be private.

    class MyClass
    {
    private:
        // For each member variable local to the class with name N and type T.
        T N;
 
    public:
        // For each function F with parameters P p
        static void F(OP<MyClass> o, P p)
        {
            MyClass &This = o->GetImplementation();
            ...
        }
    }

[edit] Instance

The instance class is instantiated to represent the class and its inheritance structure. It contains an implementation class member variable for each inherited class.

    namespace Instance
    {
        class MyClass : public Object
        {
        private:
            static Identity::MyClass StaticIdentity;
        public:
            // For each class X inherited without renaming.
            class X m_X
 
            // For each class X inherited with name path Y.
            class X m_X_Y;
 
            MyClass() : Object(StaticIdentity) {}
        }
    }

[edit] Interface

The Interface struct acts like a VTable to allow functions to be called. However it is implemented as a struct so that the function pointers may refer to renamed functions.

The offset member variable allows the implementation class instance to be obtained from the object pointer.

    namespace Interface
    {
        struct MyClass
        {
            // The offset in bytes from the object pointer to the implementation. 
            long offset;
 
            // For each function F with parameters P
            virtual void (*F)(FP<Object> o, P p) = 0;
        }
    }

[edit] Identity

The identity class implements all the inheritance structure and allows it to be accessed from another project.

It sets up the VTable interfaces, and allows them to be retrieved. It also implements the "router" functions.

    namespace Identity
    {
        class MyClass : public ObjectIdentity
        {
        public:
            // For each inherited class X that is not renamed.
            Interface::X vtable_X;
 
            // For each class X, renamed with name path Y .
            Interface::X vtable_X_Y;
 
            MyClass()
            {
                // For each class X inherited without renaming.
                // Calculate the offset in bytes from the instance class to class implementation of X.
                vtable_X.offset = (long) &Instance::MyClass::m_X;
 
                // For each inherited class X, renamed with name path Y.
                // Calculate the offset in bytes from the instance class to the class implementation.
                vtable_X_Y.offset = (long) &Instance::MyClass::m_X_Y;
 
                // For each class X inherited without renaming
                //    For each function F.
                vtable_X.F = X::F;
 
                // For each inherited class X, renamed with name path Y.
                //    For each function with internal name I and external name E.
                vtable_X_Y.I = MyClass::E;
            }
 
            void *QueryInterface(ObjectIdentity *identity)
            {
                // For each class X inherited without renaming.
                if (identity = Instance::X::StaticIdentity)
                {
                    return (void *) vtable_X;
                }
                return 0;
            }
 
            bool IsA(ObjectIdentity *identity)
            {
                // For each class X inherited without renaming.
                if (identity = Instance::X::StaticIdentity)
                {
                    return true;
                }
                return false;
            }
 
            bool HasA(ObjectIdentity *identity)
            {
                // For each class X inherited with renaming.
                if (identity = Instance::X::StaticIdentity)
                {
                    return true;
                }
                return false;
            }
 
            // For each inherited function with name F.
            static void F(OP<Object> o, P p)
            {       
                // For each class inherited without renaming.
                if (preconditions on p)
                {
                    X::F(OP<X>(o.pointer, m_X), o, p);
                }
 
                // For each class X, name path Y which implements function with external name F and internal name I.
                if (preconditions on p)
                {
                    X::I(OP<X>(o.pointer, m_X_Y), o, p);
                }
            }
        }
    }

[edit] Links

Personal tools
Variants
Actions
Navigation
Community
Toolbox