CHAPTER 10    Classes and objects

 

Visual Prolog contains a powerful object-mechanism, that melts together the logic programming and object oriented programming (OOP) paradigms.

You will find some small examples of Visual Prolog programs, that uses OOP-technlogy on the CD in the directory ¡¬OOP¡¬EXAMPLES.

Four criterias have to be fulfilled, before a system can be considered to be object orientated: encapsulation, classes, inheritance, and identity.

¢Ã Encapsulation

The importance of encapsulation and modularity are well known. Encapsulated objects can help building more structured and readable programs because objects are treated like blackboxes. Look at complex problems, find a part, which you can declare and describe. Encapsulate it in an object, construct an interface and continue so, until you have declared all the sub problems. When you have encapsulated the objects of the problem, and ensured that they work correctly, you can abstract from them.

OOP is also sometimes known as data-driven programming. You can actually let the objects themselves do the work for you. They contain methods, which are invoked, when they are created, deleted and whenever you call them. Methods can call methods in other objects.

¢Ã Objects and classes

The way data is stored in traditional programming languages is usually hard to grasp for humans and not suited for modelling. Objects are much easier to work with, because it is closer to the way humans understand real-world objects and in fact a tool for modeling in it self.

Object is a far more complex data structure than lists. An object is at the basic level a declaration of coherent data. This declaration can also contain predicates, which work on these data. In OOP-terminology these are called methods. Each class type represents a unique set of objects and the operations (methods) available to create, manipulate, and destroy such objects.

A class is a definition of an object. An instance is an actual occurrence of this object. Normally you can define as many instances as you like of a class.

Example

    class automobile
        owner string
        brand string
    endclass

    Actual instance 1
        Owner Beatrice
        brand Morris Mascot
 
    End

    Actual instance 2
        Owner John
        brand Rolls Royce
 
    End

Inheritance

OOP is a powerful modeling tool. Objects can be defined on the abstraction level that suits best. From this level child-objects can be defined on lower levels, or parent-objects on higher levels. An object can inherit data and methods from objects at higher levels. Objects are thus an easy way to make very modular programs. 

Identity

A very important characteristic of objects is, that it identity remains, even though it attributes changes.

Visual Prolog Classes

Defining a Class in Visual Prolog requires two things: a class declaration, and a class implementation. The class declaration specifies the interface to the class, what can be seen from the outside. The class implementation contains the Prolog clauses for defining the actual functionallity for the class.

The declaration of the interface to a class and the actual definition of the clauses for a class are separated. The Class declarations will often be placed in header files which can be included in places which uses the class.

Class declarations

A simplified "first-look" syntax for a class declaration is:

    CLASS class-name [: parentclass-list ]
        PREDICATES
        predicatedeclaration-list
        FACTS
        factdeclaration-list

    ENDCLASS

The optional parentclass-list specifies the parent class or classes from which the class class-name will derive (or inherit) predicates and facts (methods and objects). If any parent classes are specified, the class class-name is called a derived class.

Class implementation

A simplified "first-look" syntax for a class declaration is:

    IMPLEMENT class-name [: parentclass-list ]
    PREDICATES
        predicatedeclaration-list

        FACTS
        factdeclaration-list

        CLAUSES
        Clause-list

    ENDCLASS

The definition of the clauses for a class is done in a section starting with the IMPLEMENT keyword and ending with the ENDCLASS keyword. Inside the class implementation can be multiple Predicates, Facts and Clauses sections.

Unless the Predicates and Facts sections are preceded with the keyword STATIC, the declarations work like they where given in the class declaration, meaning that the facts will belong to the instance, and the predicates will carry the invisible object pointer. However things declared in the implementation will be entirely private to the class implementation.

Note also, that it is possible to inherit classes down in the implementation, thus encapsulating details on the class implementation.

Class instances

When a class declaration and definition has been made a multiple number of instances of this class can be created. A new instance for a class is made with a call to new for that class. New will return a reference to the instance, which can then be used to perform operations on the object.

Example:

The output from the program in goal mode will be:

    O=145464, Initial=0, NewVal=1
    1 Solution

Objects will survive a fail. Each instance of the class will have its own copy of the facts database. Such a database can be manipulated as usual by retract, assert, save, consult etc.

Note that specific new-predicates can be defined. These are called constructors and will be explained later.

Destroying Objects

Error! Bookmark not defined. Objects have to be deleted explicitly by calling delete for the object.

    O = customers::new,
    O:change_account,
    O:delete.

Deleting an object will cause an automatic retracting of all facts in the database for that instance.

Note that specific delete-predicates can be defined. These are called destructors and will be explained later.

Class Domains

The declaration of a class generates a domain with the name of the class. This domain can be used to declare parameters for predicates that should handle a reference to the object.

    CLASS customer
    .....
    ENDCLASS

    PREDICATES
      p(customer)

Passing an object in a parameter means just passing a pointer to the object as in normal OOP-programmingstyle.

Sub-classing and inheritance

In a class, both parent class predicates and global predicates can be redefined. For example can the global predicates beep, concat etc. be overridden inside a class.

When using the child class, it is possible to use predicates and facts from both the parent class and from the child class, however, the child class might choose to redefine some of the predicates or facts.

    CLASS person
        FACTS
        name( STRING )
        father( person )
        mother( person )

        PREDICATES
        write_info()

    ENDCLASS

    CLASS employe : person
        FACTS
        company(STRING Name) 

        PREDICATES
        write_info()

    ENDCLASS

    IMPLEMENT person
    CLAUSES
        write_info():-
        name(X),write("Name=",X),nl,fail.
        write_info():-
        father(F),write("Father:¡¬n"),
        F:person::write_info(),fail.
        write_info():-
        mother(M),write("Mother:¡¬n"),
        M:person::write_info(),fail.
        write_info().
    ENDCLASS

    IMPLEMENT employe
        CLAUSES
        write_info():-
        this(O),
        O:person::write_info(),fail.
        write_info():-
        company(X),write("Company=",X),nl,fail.
        write_info().
    ENDCLASS

    GOAL
        F = person::new(),
        assert(F:name("Arne")),
        O = employe::new(),
        assert(O:name("Leo")),
        assert(O:father(F)),
        assert(O:company("PDC")),
        O:write_info(),
        O:delete().

The formal syntax for using the members of an object is:

    [ObjectVariable:] [name_of_class:] name_of_member[(  list_of_arguments  )  ]

The object can be omitted inside the implementation of a class or for calling the members, which were declared as static. It will be considered as a call to the corresponding member of that class (or its parent) in case it exists. Otherwise, if there is no member with given name, it will be considered as a call to the predicate with same name which must be previously declared in some PREDICATES- or DATABASE section.

Names for members may be redefined in the hierarchy of classes. So, in order to refer to names of members in the previous scope, the name of class defined in call to some member may be used for explicit qualification of class in use.

Virtual Predicates

In Visual Prolog all Class predicates are what is in the C++ terminology called Virtual methods. Virtual methods allow derived classes to provide different versions of a parent class method. You can declare a method in a parent class and then redefine it in any derived class.

Assume, that a parent class P contains a who_am_i, and class D, derived from P, has definitions for the predicate who_am_i. If who_am_i is called for an object of D, the call made is D: who_am_i, even if the access is via a reference to P. For example:

The output from the above program would be:

    I am of class D

Note, that if you define a predicate in a subclass with different domains or number of arguments, Prolog will treat this as a different declaration, and it will not work as a virtual predicate.

Static facts and predicates

It is possible to declare predicates or facts as being static, which for facts means that the facts are not generated for each instance but there exists only one version for the class. This is useful for example to count the number of instances for a class. Preceding a predicate with the keyword static means that it will not carry the invisible extra argument, which is a pointer to the actual instance.

Example:

 

    CLAUSES

    CountInstance( 0 ).

 

    new( ):-

    countInstance( Num ),

    NumNext = Num +1,

    assert( countInstance( NumNext ) ),

    writef( "Count = %d¡¬n", NumNext ).

 

ENDCLASS

 

GOAL

    NewObject = count::new(),

    NewObject1 = count::new().

The output of this program will be:

    Count = 1
    Count = 2
    1 Solution

Class Scopes

A predicates scope is defined as the area, in which you can access it. Predicate and fact names may be redefined in the class hierarchy. In order to refer to names in the previous scope the class-name::name() notation can be used to do an explicit naming.

    CLASS parent
        PREDICATES
        p(INTEGER)
    ENDCLASS

    CLASS child : parent
        PREDICATES
        p(STRING, INTEGER)
    ENDCLASS

        % IMPLEMENTATION not shown for clarity

    GOAL
        O = child::new,
        O : parent:p(99)
  % Access the definition in parent

Another usage of the explicit scoping is in using classes with static predicates and static facts as packages, like a module system:

Example:

    ILIST = INTEGER*

    CLASS list
        static PREDICATES
        append(ILIST, ILIST, ILIST) - (i,i,o)
        ILIST gen(INTEGER)
    ENDCLASS

    IMPLEMENT list
        CLAUSES
        append([],L,L).
        append([H|L1],L2,[H|L3]):-
        append(L1,L2,L3).
        gen(0,[]):-!.
        gen(N,[N|L]):-
        N1=N-1,
        L = gen(N1).
    ENDCLASS

    GOAL
        L1 = list::gen(3),
        L2 = list::gen(5),
        list::append(L1,L2,L3).

Constructors and Destructors

The Visual Prolog system will itself allocate and initialize the memory during creation of an object. However there might still be the desire to specify how an object is created, for instance initialize facts to special values, create a window on the screen or open a file. In the same way, a programmer may wish to control, how an object is deleted, for instance closing windows or files. User defined predicates for creating of deleting objects are called constructors and destructors. A constructor is made by giving a definition for the predicate new for an object, and a destructor is made by giving a definition for the predicate delete for an object. In the clauses for the new predicate, It is possible to refer to the constructors of the base class by baseclass::new.

    CLASS mywind : wind
        PREDICATES
        new(INFO,Color)
    ENDCLASS

    IMPLEMENT mywind
        CLAUSES
        new(Info,Color):-
        wind::new(... , ...,Color),
        assert(info(INFO)).
    ENDCLASS

    GOAL
        O = mywind::new(¡°info¡±,blue),
        .....
        O:delete.

Note! Any base class constructors must be called before doing any references to the object, the compiler will check this.

The constructors and destructors are not allowed to fail, they are implicitly declared as procedures.

If a constructor or destructor exits with a runtime error, the state of the object is undefined.

new will automatically return a reference to the created instance.

Reference to the Object Itself (This)

All the non-static predicates of an object have an invisible (to the programmer) extra parameter, which is a pointer to the object.

In a clause like:

    IMPLEMENT x
        CLAUSES
        inc:-
        count(X),
        X1=X+1,
        assert(count(X1)).
    ENDCLASS

The object is entirely invisible. If it is necessary to refer to the object itself for instance to access a predicate in an inherited class, it is possible to use the built-in predicate this. The predicate allows for an instance to get access to any member-predicate, which is defined, in corresponding class or in its parents. The syntax for making call to this predicate is:

    this (  name_of_variable  )

The usage of the predicate is allowed only in predicates, which were declared as not static. This predicate has the single output-parameter.

For example:

    IMPLEMENT x

    CLAUSES
        inc:-
        this(Object),
        Object:x::count(X),
        X1=X+1,
        assert(count(X1)).
    ENDCLASS

This piece of code is functionally identical with the piece of code just above, with the only difference, that you create a pointer to this object. This can be passed on as a parameter to other predicates.

Abstract Classes

An abstract class is a class definition without an implementation. It is only inherited by subclasses. The purpose of an abstract class is to have a declaration of some predicates in order to have some other predicates working on a different specialization of the more general class. An abstract class is defined by the keyword ABSTRACT. In case an abstract class inherits some base classes, these must also be declared abstract.

As example if you want to create a browser that can work on many different kinds of data, you will opening the browser by passing it in an object which it can make call to to get the data and move forward or backward. By using an abstract class, the browser knows which predicates it can make calls to.

ENDCLASS

Protected facts and predicates

It is possible to declare whether you can access the facts or predicates from outside of the class. By default all the facts and predicates are public which means that they can be called from all other predicates.

The default access rights can be changed by preceding a fact or predicates declaration with the keywords: protected. Protected means that all classes derived from the class are allowed to access the fact or predicate, but the fact or predicate can not be accessed from outside the class.

An example of the usage of protected predicates is call back event handlers, where subclasses might redefine some handling predicates, but it makes no sense to call these from the outside:

    CLASS window
        PROTECTED PREDICATES
        onUpdate(RCT)
        onCreate(LONG)
    ENDCLASS

Derived class access control

An important issue in building the hierarchies of objects correct, so that you can reuse as much code as possible, is inheritance. You can define methods on one level, and these can then be reused on lower levels.  If a class inherits from other classes, we say, that this class is a derived class. When you declare a derived class D, you list the parent classes P1, P2 .. in a comma-delimited parent class-list:

    CLASS D : P1, P2 ...

D inherits all the facts and predicates of the parent class. Redefined names can be accessed using scope overrides, if needed.

In case the name of some member can be achieved by several ways with the directed acyclic graph (DAG), the longest way is accepted.

If 2 or more predicates in a class hierarchy are named the same, we say, that this name is overloaded. We then have to consider the scope of the name, here defined as the area in which each predicate is valid. Every usage of name for any member of some class must be unambiguous (to an approximation of overloading). The access to the member of base class is ambiguous if the expression of access names more than one member. The test for unambiguous is performed before the access control.

In case the synonymously defined name is the name of any overloaded member, then the scope of overloading is performed after the ambiguous control (but before the access control). The ambiguity can be scoped with explicit qualifying name of the member by the name of the corresponding class.

All predicates from the class declaration, except new and delete, are virtual. Opposite, all predicates declared in the implementation become non-virtual.

Formal definition for classes