Language Reference/Basic Concepts
Types and Subtypes
Visual Prolog types are divided into object types and value types. Objects have mutable state, whereas values are immutable.
Object types are defined by interface definitions.
The value types include numerical types, strings and character types and compound domains (also known as algebraic data types). Simpler forms of compound domains are structure and enumeration types, whereas more complex forms represents tree structures.
Types are organized in a subtype hierarchy. Subtypes are used to introduce subsumption-polymorphism: Any context that expects a value of some type will equally well accept a value of any subtype. Or if we turn it around, we can say, that values of a certain type are automatically converted to any super-type where needed and can thus be used as having the super-type without explicit type conversion.
Subtypes can be derived from any value type, except from algebraic data types. Types derived from algebraic data types are synonym types rather than subtypes, i.e. they are the same type rather than a subtype.
The notion of subtypes relates closely to the notion of subsets. But it is important to notice that even though a type is "mathematically" a subset of another type it need not be a subtype. A type is only subtype of another type if it is declared to be so.
domains t1 = [1..17]. t2 = [5..13]. t3 = t1 [5..13].
t1 is an integral type whose values are the integers from 1 to 17 (both inclusive). Likewise, t2 contains the values from 5 to 13. So t2 is a subset of t1, but t2 is not a subtype of t1. On the other hand, t3 (which contains the same values as t2) is a subtype of t1, because it is declared to be so.
The language contains a few implicit subtype relations, but otherwise subtype relations are explicitly stated in type definitions.
Object types are organized in a subtype hierarchy rooted in the predefined object type object, i.e. any object type is a subtype of object. Object subtypes are defined by means of stating that one interface supports another. If an object has an interface/object type that supports a certain other interface, then the object also has that type and can without further interference be used as such an object.
See also: Universal and Root Types
The description in this section is not supposed to be an introduction to classes; it is intended as a clarification of the class notions in Visual Prolog. The reader is expected to be familiar with common class notions. The description is purely conceptual in the sense that it does not refer to any syntax, implementation, etc. Moreover, this description does not consider any operational or programmatic aspects. While the reasons for introducing classes, objects, etc are mostly of programmatic nature, we find it valuable to describe the underlying concepts without reference to these programmatic reasons.
The class concept of Visual Prolog is based on the following three semantic entities:
An object is set of named object member predicates and a set of supported interfaces. Objects actually also have a state, but this state can only be changed and observed through the member predicates. We say that the state is encapsulated in the object.
Here encapsulation means the ability of an object to hide its internal data and methods, making only the intended parts of the object programmatically accessible. The importance of encapsulation and modularity is well known. Encapsulation helps building more structured and readable programs, because objects are treated like black boxes. Look at a complex problem, find a part, which you can declare and describe. Encapsulate it into 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.
An interface is an object type. It has a name and defines a set of named object predicates.
Interfaces are structured in a supports hierarchy (the structure is a semi-lattice rooted in the interface object). If an object has a type denoted by an interface, then it also has the type of any supported interfaces. Therefore, the supports hierarchy is also a type hierarchy. An interface is a subtype of all its supported interfaces. We also say that the object supports the interface. If the interface is named X, then we say that the object is an X, or an X object.
A class is a named object factory; it can create objects corresponding to a certain interface. Any object is created by a class, if an object was created by the class, which uses the interface C to construct objects then we call it a "C object".
All objects that are constructed by a certain class share the same definition of the object member predicates, but each object has its own state. Thus, the object member predicates is actually part of the class, whereas the state of the object is part of the object itself.
A class also contains another set of named predicates and an encapsulated state, known as the class members and class state, respectively. The class members and the class state exist on a per class basis, whereas the object members and object state exist on a per object basis. The class state can be accessed both by class members and by object members.
Notice! The set of object member predicates that a class defines is the union of the predicates declared (transitively) in the interfaces of that class. More specifically this means that, if the same predicate is declared in two different interfaces then the class will only provide one definition of that predicate. So the class only sounds if that makes sense, i.e. if the intended semantics of these two inherited predicates are the same.
Notice that interface support must be specified explicitly. The fact that some class provides the predicates corresponding to some interface does not imply that the class supports the interface.
In fact a class need not be able to manufacture objects at all. Such class may have only class members and the class state. And therefore, such class can be considered to be a module rather than a class.
Every object is unique: objects have a changeable state and since the state of the objects can be observed by means of their member predicates an object is only identical to itself. I.e. even if the states of two objects are identical, the objects are not identical, because we can change the state of one object without changing the state of the other object.
We never have direct access to an object state, we always access an object state by means of a reference to the object and while an object is only identical to itself, we can have many references to the same object. Thus, the same object can be accessed through many different references.
Classes and interfaces are also unique; they are identified by their names. Two interfaces or classes cannot have the same name in the same program. A class and an interface can only have the same name if the class constructs objects of that interface.
The essence is that structural equality does not imply identity for objects, classes nor interfaces.
Where the previous section described objects, classes, and interfaces in terms of their external behavior, this section will extend this description with internal issues. These internal issues have more programmatically nature; they are concerned with splitting of classes onto the declaration part and the implementation part.
From a programmatic point of view, classes are the central item: the code is contained in the classes.
Interfaces mainly have static importance. In fact, interfaces only exist in the textual representation of a program; there is no (direct) runtime representation of an interface.
Objects, on the other hand, have mainly dynamic importance. Objects are not directly visible in the program; they do not exist until the program actually runs.
A class consists of a declaration and an implementation. The declaration declares the public accessible parts of the class and the objects it generates. The implementation on the other hand defines the entities declared in the class declaration. The basic implementation of predicates is of course clauses, but predicates can also be defined by means of inheritance, or resolved to external libraries.
A class declaration in Visual Prolog is purely declarative. It only states which entities you can access: not how or where they are implemented.
A class implementation can declare and define further entities (i.e. domains, predicates, etc), which are only visible inside the class itself. I.e. they are private.
The state of an object is stored in the object as its facts. These facts are declared as normal facts (database) sections in the implementation of the class. Facts are local to each object (like other object entities), whereas class facts are shared among all objects of the class.
Facts can only be declared in the implementation of a class and, therefore, cannot be accessed (directly) from outside the class.
The implementation of a class can also declare that it supports more interfaces than mentioned in the declaration. This information is, however, only visible in the implementation itself and is, therefore, private.
In Visual Prolog code inheritance only takes place in the implementation of a class. Visual Prolog has multiple inheritance. You inherit from a class by mentioning the class in a special inherits section of the implementation. The classes you inherit from are called parent classes or super-classes. Child-class or sub-class is the dual to parent class, we also say that the child classes inherit from the parent classes. A child class can only access its parent classes through its public interface, i.e. it does not receive any extra privileges than anybody else that use the parent class.
Scoping & Visibility
All names (identifiers) in Visual Prolog are syntactically divided into two major groups:
- Constant names (starting with a lowercase letter)
- Variable names (starting with an uppercase letter or an underscore)
Constant names (identifiers) are divided into the following categories:
- Type names (i.e. domains and interfaces).
- Domain carriers (i.e. classes and interfaces).
- Names without parentheses (i.e. constants, fact variables of non-function type and nullary-functors).
- Value returning names of arity N (i.e. functions, functors and fact-variables of function type).
- Non-value-returning names of arity N (i.e. predicates, facts and fact variables of predicate type).
Visual Prolog demands that names do not conflict at the point of declaration, because then it would be impossible to solve the conflict at point of usage. Declarations can only conflict if they are in the same scope, because qualification with scope can be used to solve the conflict. A name in one category can never be in conflict with a name in another category, but as we shall see a single declaration might place a name in several categories.
The basic units of code organization accepted in Visual Prolog are packages. We use packages in Visual Prolog to organize and structuring things. The use of packages ensures homogeneity in structuring principles among different projects. Packages define the standard for tool structuring and ease sharing source code among projects.
The package is a collection of several grouped together interfaces and classes. The package provides some common name to all these interfaces and classes. Each declaration or implementation of each interface or class from a package is placed in a separate file. Each filename (of these files) coincides with the name of a class or an interface that is declared or implemented in this file. All package files are stored in the same separate package directory. (If a package contains sub-packages, then they are placed in subdirectories of the package directory.)
The concept of packages is used for grouping together several linked interfaces and classes. Packages can play a role of some class library. Packages can be used in your program instead of direct placing all used interfaces and classes into your program.
The accepted in Visual Prolog structure of packages and how packages should be included into projects are described in the VDE part of this help. (See Creating a Package in Creating New Project Items.)
Visibility, Shadowing, and Qualification
Most of the scoping rules are already mentioned above. This section will complete the picture.
An interface definition, a class declaration and a class implementation are scopes (scopes cannot be nested). An implementation (privately) extends the scope of the corresponding class declaration. The visibility is the same everywhere in a scope. This does especially mean that no matter where in a scope something is declared, it is visible in the whole scope.
Public names from supported interfaces and super-classes are directly (i.e. without qualification) available inside a scope, if it is unambiguous where they come from. It is illegal to use a name whose origin is ambiguous. All ambiguities in predicate calls can be removed by qualifying the predicate name with the class name (e.g. cc::p).
This qualification is also used to qualify calls of object member predicates of super-classes on the current object.
Visual Prolog has the following shadowing hierarchy:
- Super-class & opened scopes
Opened scopes have the same status as super-classes, so in the sequel we will just say super-classes.
The hierarchy means that a local declaration will shadow a super-class declaration. But there is no shadowing between super-classes; all super-classes have equal preference. If two or more super classes contain conflicting declarations then these declarations can only be accessed by means of qualification.
interface ixx predicates p1 : (). p2 : (). p3 : (). end interface ixx %======================== class aa : ixx end class aa %======================== class bb predicates p3 : (). p4 : (). p5 : (). end class bb
The class aa creates objects of type ixx, whereas the class bb does not create objects at all.
In this context consider the implementation of a class cc:
implement cc inherits aa open bb clauses p2(). % reimplementation of ixx::p2 predicates p5 : (). clauses p5(). clauses new() :- p1(), % aa::p1 p2(), % cc::p2 (shadows aa::p2) aa::p2(), % aa::p2 p3(), % Illegal ambiguous call: aa::p3 or bb::p3 aa::p3(), % aa::p3 bb::p3(), % bb::p3 p4(), % bb::p4 p5(). % cc::p5 (shadows bb::p5) end implement cc