10.13.3.3 Asking About Classes and Objects

It is possible to determine, at run time, what classes are defined, how they are related by inheritance, what class an object belongs to, etc. This section describes the predicates used for those purposes. Most of the predicates involve the class hierarchy, so they are properly described in the section on inheritance. But, several can be useful even in programs that use only simple classes.

Most of these predicates come in pairs, where one predicate involves one class or its direct superclasses, and the other predicate involves all ancestors. For example, the class_superclass/2 and class_ancestor/2 predicates connect a currently defined class to its superclass(es) and to all its ancestors, respectively.

In all of these predicates, the ancestors of a class include not only superclasses and their ancestors, but also the class itself. A class cannot be a superclass of itself, by the rules of defining classes. However, it is convenient to consider every class an ancestor of itself, because then we may say that every property of a class is defined in one of its ancestors, without having to say “the class itself or a superclass or a superclass of a superclass, etc.”

Objects

The class_of/2 predicate is used to test whether an object is of a particular type or to determine the type of an object. Similarly, the descendant_of/2 predicate relates an object to all ancestors of its class. (Remember that the object's class is, itself, an ancestor class of the object.)

Both require the first argument (the object) to be instantiated. That is, the predicates cannot be used to find objects of a given class. If you need to search among all the objects of a class, you must provide a way to do it. One way to do this is to assert a fact connecting the class name to every object, when it is created. The named_point example of the previous section took that idea a step further by allowing each object to have a different name.

The pointer_object/2 predicate relates an object's address (a pointer) to the object. Remember that an instance of Class is represented by a term of the form

     Class(Address)

The pointer_object/2 predicate requires that one of its arguments be instantiated, but it may be either one. Hence, just by knowing the address of an object (which possibly was returned by a foreign function) it is possible to determine the object's type.

Most Prolog programmers can safely ignore the pointer_object/2 predicate, unless they are using SICStus Objects with foreign functions or with the Structs package.

Classes

The current_class/1 predicate is used to ask whether a class is currently defined or to get the names of all currently defined classes.

The class_superclass/2 predicate is used to test whether one class is a superclass of another, or to find a class's superclasses, or to find a class's subclasses, or to find all subclass-superclass pairs. The class_ancestor/2 predicate is used in the same ways for the ancestor relation between currently defined classes.

As an example, the following goal finds all the ancestors of each currently defined class.

     | ?- setof(C-As,
     	   (current_class(C),
     	    setof(A, class_ancestor(C,A), As)),
     	   L).

It binds L to a list of terms of the form Class-AncestorList, with one term for each currently defined class.

Arguably, this predicate violates the principle of information hiding, by letting you ask about how a class is defined. Therefore, you should generally avoid it. It may be useful, however, in debugging and in building programmer support tools.

Messages

The message/4 predicate is used to ask whether a message is defined for a class or to find what messages are defined for a class, etc. It does not distinguish between messages whose methods are defined in the class itself and those that are inherited from a superclass.

The direct_message/4 predicate is used to ask whether a message is not only defined for a class, but whether the method for that message is defined in the class itself. It can also be used to determine which methods are defined in a class. This ability to look inside a class definition makes direct_message/4 an egregious violator of the principle of information hiding. Thus it, like class_ancestor/2, should mainly be confined to use in programmer support applications.

Both message/4 and direct_message/4 take the message operator as an argument, along with the class, message name and arity. Hence it is possible to use these predicates to ask about get, put or send messages.

It is not possible to ask about a class's slots, nor should it be. However, it is possible (and quite reasonable) to ask about the get and put messages that are defined for a class. For example, the following goal finds all the 1-argument messages that are defined for both the get and put message operators in the class Class.

     | ?- setof(Message,
     	   (message(Class, <<, Msg, 1),
     	    message(Class, >>, Msg, 1)),
     	   L).

There may or may not be slots corresponding to these messages; that detail is hidden in the definition of Class. However, it should be possible to use Class as if the slots were there.

As an example, recall the polar coordinate interface to the point class, which defined get and put methods for r and theta, even though data was represented inside an object by rectangular coordinates x and y.


Send feedback on this subject.