View and Construction Types
An interface defines a type of objects, all objects that supports the interface have that type. In the sequel we shall use the term the object type as synonym for the term the type defined by the interface.
Since interfaces define types, these types can be used as formal type specifiers in predicate and fact declarations and in domain definitions.
Since an object have the object type of any interface it supports, it can be used as an object of any of these types. I.e. the type of objects is converted to any supported object type. As described below the conversion is in many cases performed automatically.
So the same object can be viewed as having different types in different contexts. The type with which an object is viewed is called its view type, whereas the type of the class that constructed the object is called the construction type or definition type. The construction type is also a view type.
As mentioned above objects can be used as having the type of any supported interface. This section describes how conversions between various supported types is handled.
If some term is statically typed to some type T1 and T1 is declared to support T2 then it is obvious that any object referenced by the variable will indeed support T2. So upward support information is statically known. Subsequently all conversions upwards in the support hierarchy is performed automatically.
implement ... predicates ppp : (aa AA). clauses ... :- BB = bb_class::new(), % (1) ppp(B). % (2) clauses ppp(AA) :- % (3) ... BB = convert(bb,AA), % Conversion possible since definition type of AA is bb ...
In the line marked (1) we create a bb_class object: the object has construction type bb. The variable BB is a reference to this new object. BB provides the view type bb on the object. In the line marked (2) the object is passed to ppp as an aa object. The conversion from view type bb to view type aa is performed implicitly. When the object reaches the line marked (3) it has view-type aa, though the construction type is still bb.
Explicit conversion is performed by calling a conversion predicate.
Several conversion predicates are available.
Neither predicate can be given a real declaration, but here are pseudo declarations for them
predicates convert : ("type" Type, _ Value) -> Type ConvertedValue. tryConvert : ("type" Type, _ Value) -> Type ConvertedValue determ.
- converting from one number domain to another number domain
- converting an object to another type
The compiler may complain (but does not have to) if it can determine that a conversion can never succeed, for example if attempting to convert between number domains that does not have overlapping ranges.
When an object is converted to a super type (i.e. to a supported interface), then information about the object is "forgotten". Notice that the capabilities are not really lost they are just not visible in the context where the object is seen with a less capable interface.
In many situations it is necessary to restore the actual capabilities of the objects. Therefore, we need to be able to convert them downward as well as upwards.
Downward conversion cannot (in general) be validated statically. Therefore, it is necessary to use explicit conversion when restoring "lost" interfaces.
While it is extremely simple to make sensible illustration of type conversions up in the supports-hierarchy, it requires a "real" example to illustrate sensible use of downward conversion. Therefore we shall present a more "real" example here.
Assume that we want to implement "sets of homogeneously typed objects". I.e. sets of objects which all supports a certain view type. We know that we will need such sets for several types of objects. Therefore we want to make our implementation in a way, which can easily be adopted to many different types of objects, but yet preserve the homogeneity of the contained objects.
Our approach is fairly standard: we make the actual implementation of the "set" based on the object type, which any object supports. And then construct the more specific versions of "sets" by means of a thin layer, which will convert between the actual type and object. We shall not show the actual implementation of object sets, we shall merely assume that it exists in the shape of the following class and interface:
interface objectSet predicates insert : (object Elem). getSomeElem : () -> object determ. ... end interface class objectSet_class : objectSet end class
Now assume that we have some object type myObject and that we want to create the corresponding "set" class myObjectSet_class. We declare myObjectSet_class as following:
interface myObjectSet predicates insert : (myObject Elem). getSomeElem : () -> myObject determ. ... end interface class myObjectSet_class : myObjectSet end class
I.e. myObjectSet has all the predicates of objectSet but every occurrence of object is replaced with myObject. The implementation of myObjectSet_class inherits from objectSet_class, this embedded/inherited objectSet will carry the members of the set. The implementation will fulfill the following invariant: The embedded objectSet will only contain objects of type myObject (even though they "technically" have type object).
The implementation looks as follows:
implement myObjectSet_class inherit objectSet_class clauses insert(Elem) :- objectSet_class::insert(Elem). % (1) getSomeElem() = Some :- SomeObject = objectSet_class::getSomeElem(), % (2) Some = convert(myObject, SomeObject). % (3) ... end implement
In the line marked (1) Elem is automatically converted from type myObject to object. In the line marked (2) we retrieve an object from the embedded object set. Technically this object has type object. But from our invariant we know that the object also supports myObject. Subsequently, we know that we can safely restore the myObject interface. This is explicitly done in the line marked (3).
Private and Public Types
When an object is created with a constructor it is returned with the construction type. Such an object can automatically be converted to any supported interface and explicitly back again.
Even if the class that implements the object have stated further supported interfaces privately it is impossible to convert the "public" object to any of these private types.
In the implementation however the object can be accessed with any privately supported type. Furthermore "This" can be handed outside the implementation with any of these privately supported types.
Such a "private" version of an object can also be converted implicitly upwards in its hierarchy and explicitly downwards again. In fact such a "private" object can be converted explicitly to any publicly or privately supported interface.
So an object have two views the public view and the private view. The private view includes the public type. The object cannot be converted from one view to another, but since the private view includes the public type, the private view can be converted to any supported type whatsoever.
The predicate uncheckedConvert/2-> is used to perform unsafe conversions based on memory representation. The predicate does not modify memory in any way, it simply forces the compiler to interpret that piece of storage with another type.
Notice this predicate is highly unsafe and should be used with maximum precautions.
The predicate is intended to be used when interfacing to foreign languages, in order to interpret the memory images these foreign languages uses.
uncheckedConvert/2-> can only be used on pieces of memory that have exactly the same bit-size. However many kinds of data are represented by a pointer and such data have the same bit-size.
predicates uncheckedConvert : ("type" Type, _ Value) -> Type ConvertedValue.
predicates interpretBufferAsString : (pointer BufferPointer) -> string Value. clauses interpretBufferAsString(BufferPointer) = uncheckedConvert(string, BufferPointer).
This predicate will interpret (convert) a buffer represented by a pointer as a string.
This is only sound if the memory block has the correct representation to be a string.