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.”
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.
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.
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
.