Symbolic Logic:Programming:Object Oriented Logic Programming

From Knowino
Jump to: navigation, search

Object Oriented Programming may be regarded as a combination of,

Inheritance is based on set theory, with an additional logical exception system (function overriding).

Contents

[edit] Modularity, Abstraction and Encapsulation

The basis for OO is that functions and data belong together to describe a single object that represents the behaviour of a real world entity.

To support this syntactically functions are defined in a single class definition. Data variable and functions of the class are accessed as members. In member functions the class is an implicit first parameter to the function.

    name.Equals("Roger")

is used instead of,

    String.Equals(name, "Roger")

Logically both these calls should mean exactly the same thing, but in most OO languages these are calls to different signatures.

The signature of a function is the combination of name and other information that uniquely identifies a function, in a language. The definition of a signature differs from language to language.

Abstraction and Encapsulation is the hiding of member functions and member variables so that they may only be accessed from certain scopes. Member functions and variables may be

Class inheritance (described in the next section) introduces two other forms of scope restrictions.

Both these are useful and significant meanings and it is unfortunate that, in the java language, the same word is re-used for a different meaning.

[edit] Classes and Set Theory

Inheritance is related to set theory, but with an exception system (for overriding functions).

[edit] Inheritance - "Is A" relationship

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 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 Inheritance Theorem

A statement made about a set is true for all subsets. This may be expressed in logic as,

\forall f \in function, p, c \in set : (c \subset p \implies ((\forall x \in p : f(x)) \implies (\forall y \in c : f(y)))) \!

This theorem is easily proved using, (X \subset Y and x \in X) \implies x \in Y \!

For classes, this theorem means that a function that is defined for a class is also defined on any inherited class.

This statement involves quantification over functions which is not allowed in first order logic. So this is second order logic.

The inheritance theorem gives the parent class a way of calling functions a derived class. This means that the parent class and the inherited class may have a two way interaction.

Suppose a class Club represents a club that you may join if you meet certain conditions. The membersip condition might be called ElligibleForMembership.

Then there might be a club for dog owners called DogOwnersClub which inherits from Club.

  class Club
  {
    bool Join(Person &p)
    {
      bool result = false;
      if (ElligibleForMembership())
      { // sign up for membership
        ...
        return result;
      }
    }
  }
  class DogOwnersClub : Club
  {
    bool ElligibleForMembership(Person &p)
    {
      return p.OwnsADog();
    }
  }

The Club class may call the DogOwnersClub::ElligibleForMembership function without ever knowing that there is a DogOwnersClub class. Also the DogOwnersClub class has access to all the members of Club. This two way relationship is very useful in writing general re-usable code.

[edit] Inheritance - "Has A" relationship

A "Has A" relationship differs from an "Is A" relationship in that there is no set membership implied. A dog has a leg does not mean that a dog is a leg. What it means is that part of a dog is a leg. We might say that a dog has a dog leg, and that a dog leg is a leg. In fact a dog has four legs,

 Dog.HasA(Leg, LeftFront)
 Dog.HasA(Leg, RightFront)
 Dog.HasA(Leg, LeftRear)
 Dog.HasA(Leg, RightRear)

How does the leg communicate with the dog? With an inheritance relationship the parent class has access to call methods on the dervived classes. The same functionality is useful in a "Has A" relationship.

Suppose the leg has a method FeelsPain. We would like calling this method on the leg to invoke a response on the Dog. But because there are 4 legs there would need to be 4 methods implemented on the dog,

These names are constructed using the name pattern FeelsPain% where % is replaced by the name of the leg. The functions Name and Rename map the name pattern to the name.

  Constructs the function name by removing the %.
 Constructs the function name by replacing the % by the name.

Using these functions a rule similar to the Theorem of Inheritance which may be applied to HasA relationships,

\forall f \in pattern, p, c \in set, n \in name,  x \in p  : \!

(Name(f)(x) \implies (\forall y \in c : x.HasA(y, n) \implies Rename(f, n)(y))) \!

This extension of the Theorem of Inheritance is the axiom of renaming inheritance.

[edit] Using Renaming Inheritance

  class Leg
  {
    void FeelsPain%();
    void ExtendLeg%(long angle)
    {
      ...
      if (angle > 90)
      {
        FeelsPain();
      }
    }
  }
 
  class Dog
  {
    inherit Leg as LeftFront;
    inherit Leg as RightFront;
    inherit Leg as LeftRear;
    inherit Leg as RightRear;
 
    void FeelsPainLeftFront()
    {
      Log.Write("Feels pain in left front leg");
      ...
    }
    void FeelsPainRightFront()
    {
      Log.Write("Feels pain in right front leg");
      ...
    }
    void FeelsPainLeftRear()
    {
      Log.Write("Feels pain in left rear");
      ...
    }
    void FeelsPainRightRear()
    {
      Log.Write("Feels pain in right rear leg");
      ...
    }
  }
 
  void main()
  {
    Dog dog;
    dog.ExtendLeftFront(100);
  }

This would result in

 Feels pain in left front leg

being written to the log.

[edit] Classes and Sets

Inheritance may be seen as logic. The approximate correspondance between classes and sets is shown in the table below. The correspondance is approximate because C++ is an imperative language (and not very clean).

Id Description C++ Description Logic
1 Declare a class class X X is a set X \in allsets \!
2 Declare a variable X x x is an element of X x \in X \! (and x is a variable in this scope)
3 X inherits Y class X: virtual Y an X is a Y X \subset Y \!
4 boolean function bool X::f(P p) {return g(p);} f(x, p) implies g(x, p) x \in X \and p \in P \and f(x, p) \implies g(x, p) \!
5 method void X::f(P p) {g(p);} f(x, p) implies g(x, p) x \in X \and p \in P \and f(x, p) \implies g(x, p) \!
6 function T X::f(P p) {return g(p);} t = f(x, p) implies t = g(x, p) x \in X \and p \in P \and t \in T \and t = f(x, p) \implies t = g(x, p) \!
7 function T X::f(P p) {h(p); return g(p);} t = f(x, p) implies h(x, p) and t = g(x, p) x \in X \and p \in P \and t \in T \and t = f(x, p) \implies h(x, p) \and t = g(x, p) \!
8 result = x return x t = x \!
9 statements A; B A and B A \and B \!
10 assignment a = b; the output role a = the input role b a_{n-1} = b_n \!

There are simple laws that describe the set theory related to inheritance.

Rule Description
x \in A \cap B \iff x \in A \and x \in B \! intersection corresponds to logical "and"
x \in A \cup B \iff x \in A \or x \in B \! union corresponds to logical "or"
x \in A \and A \subset B \implies x \in B \! subset corresponds to implication

So inheritance can be defined in terms of logic.

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.

Criticisms of Object Oriented Programming

[edit] Event handlers / 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 (in C++),

    class Vault
    {
    protected:
        virtual void Alarm() = 0;
 
    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) \!

[edit] Callbacks

The previous example used inheritance as a convenience. 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 represented in logic as,

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 \!

It is not simple to code this in C++. C++ distinguishes between functions and member functions. To implement the above code would require member function pointers. Java does not support function pointers so the result must be achieved using an interface. C# has delegates for the implementation of callbacks.

From a logic point of view, a member function is a function. So,

this->Alarm() is equivalent to Alarm(this).

In any case the use of function pointers makes the code hard to understand. Also the callback functions must be initialized in the Bank class. The Bank class must do its own plumbing. When we used inheritance there was no need for call back functions at all. Is there a way of treating the "Has A" relationship so that is similar to "Is A"?

[edit] Renaming Inheritance

The Bank needs to map the methods AlarmMain and AlarmSecondary onto the Alarm method. We could think of this as,

Alarm% is a name pattern. By defining it in the Vault class we already know the names of the functions to be called.

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 Main
inherit Vault as Secondary
// In heriting from Vault as Main Bank, inherits the Alarm method which is renamed.
// The rename template Alarm% is used with % substituted with Main to give the inherited name.
void AlarmMain()
{
Logger.Write("Alarm on main vault");
Alarm();
}
// The rename template Alarm% is used with % substituted with VaultSecondary to give the inherited name.
void AlarmSecondary()
{
Logger.Write("Alarm on secondary vault");
Alarm();
}
}

The use of a name pattern gives the Vault class more control of how it is to be used. It is not dependent on function pointers being set up. The inheriting class needs only to implement the renamed functions to implement the call back.

[edit] Typical Usage

Renaming inheritance is readily applicable in many simple situations.

[edit] Get Set methods on attributes

Often developers will write Get and Set methods to allow controlled access to a member variable. These methods may be inherited from a class called Attribute, using renaming inheritance,

    class Attribute(type)
    {
    private:
        type m_Value;
    public:
        type Get() rename as Get%
        {
            return m_Value;
        }
        void Set(type value) rename as Get%
        {
            m_Value = value;
        }
    }
 
    class Person
    {
        inherit Attribute(String) as FirstName;
        inherit Attribute(long) as Age;
    }

The person class will then inherit the methods,

The main benefit of this is to put functionality into the Attribute class that would otherwise be implemented for each attribute. See Object Relational Mapping - Attributes.

[edit] Owned Lists

Where a class owns a list of objects, it will need to keep control of the list. For example the Person class may have a list of Pets. The Person class will want to control the addition of new animals to the list of pets.

    class List(type)
    {
        void Insert(type newItem) rename as Insert%
        {
            if (Validate(newItem)
            {
                // do inserting
            }
        }
        type Get(long index) rename as Get%
        abstract bool Validate(type newItem) rename as Validate%;
    }
 
    class Person
    {
        inherit List(Animal) as Pet;
 
        bool Validate(type newItem)
        {
            // validation conditions
        }
    }

The Person class inherits the functions,

The list may be given to another class for some processing. If the other class calls the Insert method the ValidatePet method will be called. So the person keeps control of the list.

[edit] Shared Functions

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] Closures

A Closure is a way of constructing a function that is useful for implementing a call back function. It provides an alternative to renaming inheritance for when the callback function is not defined by the structure of the classes.

It 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 Lambda lifting.

[edit] Mappings

A mapping is 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,

Signature Description
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 following methods implemented automatically,

     // Draw is not renamed in the Butterfly class, so calling Draw calls Draw on every element in the mapping.
     bool Draw()
     {
         result = ForEach(x | x.Draw());
     }
 
     // Name is renamed in the Butterfly class, so the Name function takes an index as a parameter to identify the butterfly.
     String Name(1..10 index)
     {
         result = [index].Name();
     }

Functions whose name is shared may 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] Class Sections

Class sections divide the functionality in a class. For example each object needs functionality for,

The sections in a class may relate to different libraries.

An instance of a class may not need every section for a particular purpose. For example if no GUI is required for a particular class instance the GUI section will not be used.

[edit] Construction and Messaging

Object Oriented programming is built on the principle that the functionality should be attached to the data. But when sending messages this is not the case.

When sending messages the whole message should be constructed in a structured form, with no functionality.

[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 small single purpose sub classes.

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