CHAPTER 3
At the outset it may not seem that difficult to merge the two models into one, yet in previous SISs where it was deemed desirable it was not accomplished [Selfridge, 1990] [Devanbu, 1994]. This provides at least an initial clue that the apparent simplicity of the problem is deceiving. Considerable time and effort over the course of this research was expended verifying the difficulty of this task and locating the source of that difficulty. The problem turns out to lie at the very foundation of Knowledge Representation and Reasoning as a computational enterprise, a place few thought to look, and its identification is leading to a new epistemology for representing software [Welty, 1995a].
Although actual usage varies, the logical foundation is clear: set membership is a unary predicate, and therefore the name of a set is a predicate symbol [Carnap, 1947] [Quine, 1964]. This point may seem obvious or irrelevant, but the simple fact is that many practitioners ignore it, and it does come into play in the realm of software representations.
Eagle(E1) Number(10) Age(E1,10)Those accustomed to reading representations will interpret this as, "
E1 is an Eagle, 10 is a Number, and E1's age is 10." This is not actually what it says according to the semantics of FOL, but because most representations follow this general scheme, implemented representation systems (such as Classic) present scaled down first order systems which allow for the definition of three special kinds of symbols: concepts, individuals, and roles.
An individual is similar to an object symbol in FOL except that it must be an individual of some concept, as with E1 and 10. A concept is a special unary predicate that denotes a set, as with Eagle and Number. A role is a binary predicate that represents a relationship between two individuals, as with age. The interpretation given above would be correct for Classic.
FORALL x Eagle(x) --> Bird(x)This would be interpreted as "All eagles are birds," which again is not entirely correct according to the semantics of FOL, but is used so frequently to mean precisely this that representation languages almost universally supply a short-hand notation for expressing this taxonomic relationship, called subclass.
Although the subclass relationship is usually expressed as a relationship between two concepts, e.g Eagle is a subclass of Bird, it is worthwhile to note that, by definition, relationships between predicate symbols (concepts are, as discussed above, predicate symbols) are second order, and it is important computationally to maintain the first order status of a representation system [Gödel, 1931]. The subclass relationship really is no more than a shorthand notation for the inference shown above.
Clearly, via modus ponens, the result of making Eagle a subclass of Bird would be that the individual E1 in the previous example would now be inferred to be an individual of Bird. This is known as superclass inheritance.
Most representation languages do not permit an individual to have individuals. Consider the implications of such a construct in FOL:
Eagle(E1) E1(E2)When
E1 is used as a predicate symbol, the predicate Eagle becomes second-order because it is the predicate of a predicate. A common pitfall of modeling in FOL is to create a two-place predicate for individual, such as:
individual(E1, Eagle) individual(E2, E1)Which, syntactically, is first order. It is not, however, completely first order because, as stated in the beginning of this section, set membership is a unary predicate. This example violates the semantics of a first order system [Carnap, 1947] [Quine, 1964].
The point here is that, despite syntactic hacks like making a predicate called individual, an individual of an individual is a second-order construct.
First order representation languages also do not allow roles between concepts. A role is a two place predicate, and a concept is a one place predicate, making the PreysOn role second order:
Pigeon(P1) Eagle(E1) PreysOn(Eagle, Pigeon) PreysOn(Eagle, P1)A concept can not, therefore, be predicated by a role (a concept can not be predicated by anything). It is common to speak of the relationship between an individual and its concept as a role, but this must be understood to be different than a role between two individuals.
Smalltalk [Goldberg and Robson, 1983] provides the ability for classes (a Smalltalk class is roughly equivalent to a Classic concept, at least for the purposes of this discussion) to have certain properties of instances (individuals). They can, like instances, be sent messages and have their own variables, which are defined as part of the meta-class description of the class. While this is second order, it is not prone to problems because this aspect of the representation is not subject to inference (Smalltalk provides only for superclass inheritance as a form of inference).
Smalltalk classes are also themselves instances of a special class named class, making them instances which can have instances. Again, this second order relationship is controlled because there is no actual inference involved, it is provided more to keep the syntax cleaner, and make the message passing paradigm pervasive in the language.
Classic employs an approach to representing roles on concepts that has been called the abstraction relationship [Brachman, 1983]. This approach involves creating, as part of the language, a special kind of individual for each concept which represents the concept as an individual. While these individuals (called meta-individuals) behave like all other individuals (having roles and being individuals of a concept called concept), they have a special relationship, the abstraction relationship (which is defined as part of the syntax of the language as the individual of relationship is), between themselves and the concept they represent. Each concept in Classic, then, can have individuals and a meta-individual.
Again, as with Smalltalk, this second order relationship can exist because it is not used in any inference.
data-type-08 represents the data-type group. There are, scattered throughout the code, a number of variables that have this data-type, and one example of such a variable, parameter-02, is shown in Figure 3.1. Looking back at Figure 2.4, we again see group in the domain model taxonomy under mail-recipient. It would seem that the ideal way to proceed would be to somehow indicate in the "merged" model that the domain concept group is the same as the code-level individual data-type-08. In Classic, the only ways to express a relationship between two objects are to link them with a role, make one an individual of the other, or make one the meta-individual of the other.
We cannot relate data-type-08 to group with a role, because the former is an individual and the latter is a concept. Any kind of link between them other than the individual link would create a second-order relationship. We do not want to make data-type-08 an individual of group because it simply isn't. Individuals of group should represent actual groups, not a data-type that has variables and is used by functions.
In essence, data-type-08 (an individual from the code model) and group (a concept from the domain model)[1] are the same object, viewed differently. One describes how groups are used by the program, the other describes groups in general. We have run into a situation in which we need to represent one object, the same object, as both a concept and an individual. Neither model would be satisfied if we simply chose one or the other. This is what the meta-individual relationship is supposed to represent, but the implementation of this relationship in Classic is a barrier: the point of representing data-type-08 as an individual is to use it in inference to make discovery easier (as will be shown in Chapter 6), but meta-individuals can not be used in inference.
This is the core of the difficulty in integrating the code model and domain model: they represent the same knowledge at fundamentally different levels: concepts and individuals. Even though the code and domain models reuse many of the same objects, they reason about them and use them differently, and to support both at the same time requires a second order representation.
The solution involves splitting second-order objects into two parts, and keeping each part in a separate universe of discourse in which it exists as a first-order object (again in Classic there are three kinds of objects: concepts, roles, and individuals). What is actually one object is being represented as two objects, and so we will refer to the actual object as a spanning object because its representation spans two universes of discourse. Each spanning object will be composed of an individual in one universe of discourse, and a concept or role in a second universe of discourse. The two parts will be linked by a special spanning function, which will be defined for each class of second order objects. This function defines how to generate the concept or role part from the individual part.
Consider, for example, the data-type problem. Again, we need to be able to use data-type-08 as an individual in the code model so that it can be linked into the program, and we need to be able to use group as a concept in the domain model so that it can have individuals and describe what groups are in the domain. We also need to be sure that the domain model concept accurately reflects what the code "knows" about groups.
To accommodate these requirements with spanning objects we create two universes of discourse, one for the code model and one for the domain model, as shown in
Figure 3.2. In the code model universe, the concept data-type is created and identified as a class of spanning objects (this means that the concept data-type is not itself a spanning object, but its individuals are), and we define a spanning function which specifies the relationship between the two parts of the spanning objects. We now create an individual of data-type, data-type-08, which is a spanning object. The second part of the object, the concept group, is then created in the second universe of discourse. The concept description for group is generated from data-type-08 automatically by the spanning function, and now individuals of group can be created in the domain model universe.
Let us consider more deeply the objects in the code model. In general we know that an object-oriented data-type is defined by its slots, methods, and its superclasses (a relationship to another data-type through which it can inherit other slots and methods). Each of these things needs to be represented explicitly in the code model universe - that is, we need concepts describing slots, methods, and a role representing the superclass relationship (we need a role here because the relationship is between two individuals of data-type, and not between two concepts). In the domain discussed so far (the taxonomy is shown in Figure 2.4), a group is a subclass of a mail recipient, and has one slot that is filled with the members of that group, all of which must be people. This could be represented, as shown in
Figure 3.3, by creating an individual of the concept slot, and making this a filler for the role has-slot in the individual data-type-08. This individual of slot will have a role called has-data-type, which is filled with the individual data-type-05, the individual that represents people. In the figure, the individuals are shown with their classic names (such as data-type-05), and the value of the string that fills their name role (such as "person").
There are two kinds of spanning objects shown in Figure 3.3: data-types and slots. A data-type maps from an individual in the code-model universe to a concept in the domain model universe. A slot maps from an individual in the code model to a role in the domain model universe. In fact, much of what is shown in Figure 3.3 maps into the domain model universe. The superclass role maps into a told subsumption relationship (in other words the role superclass maps into the built-in Classic superclass relationship between two concepts - the role superclass has no special meaning to Classic), the has-slot role maps into a role specification in the domain model universe (in other words, every individual of slot in the code model universe is a role in the domain model universe), and the has-data-type role maps into an "all" restriction, as shown in Figure 3.4.
Most of the work in creating the spanning concepts in the domain model universe is done by the spanning function for individuals of data-type. This function generates a concept description from each individual. It finds the filler for the name role (shown in Figure 3.3 under each individual) and uses this as the name of the concept. It then builds the concept description by creating a list that starts with the word and, then it finds the fillers for the superclass role and for each one gets the filler for the name role and inserts it after the and. If there are no fillers for the superclass role, the symbol classic-thing is used. Next, for each filler for the role has-slot, the filler for the has-data-type role is retrieved, and this name is used in an all restriction on the role whose name is retrieved from the name role of the slot individual.
The spanning function for individuals of slot simply creates a role definition, using the value in the name role as the name of the role.
The resulting Classic code for the domain model based on the code model objects from Figure 3.3 is shown in Figure 3.4, below:
(defrole has-members)
(defconcept mail-recipient
(and classic-thing))
(defconcept group
(and mail-recipient
(all has-members person)))
(defconcept person
(and mail-recipient))
Note that all the Classic code shown in Figure 3.4 is generated automatically from the code model by applying the spanning functions to the spanning objects (individuals of data-type and slot). In other words, the domain model is generated automatically from the code model. This implies that all the information in the domain model must be part of the code model. In fact, every domain concept must be represented as a data-type. Normally the code model represents only those aspects of data-types that are relevant to the software operation, but the consequence of having the domain model depend on the code model is that the code model must include some additional information. For example, it may not be relevant to the software that all people have a role called spouse, the software may never use this information (in other words, the slot may never get accessed in the program), yet this is important information to the domain model, so the slot must exist in the code model so that the proper domain concept can be generated.We have identified a new element of a Classic ontology: the spanning object. This requires that any Classic ontology that uses spanning objects add the specification of the universes of discourse, the spanning objects, and the spanning functions to the definition of what goes into a Classic ontology given in Section 2.2.4.
data-type and slot) that the domain ontology will be described. This section gives the specification of the code-level ontology.Again, as discussed in Section 2.2.3 and Section 2.2.4, and with the addition of spanning objects from Section 3.5.3, an ontology is composed of concepts, roles, and rules. The concepts are arranged in a taxonomy, identified as defined or primitive, given role restrictions, and if a concept is a spanning object, it is given a spanning function. Roles are arranged in taxonomies and given inverses.
software-thing: the root level concept in the code-level taxonomy. There are basically two disjoint subsets of software things, objects and actions, in other words, every individual of software-thing is either an object or an action, but never both. Some objects and some actions are also values, which is a non-disjoint (in fact clearly overlapping) subset.
software-object: these are the objects found in software systems, such as procedures (methods), programs, data-types, and variables. Every software object has a name, which is a string in the role name.
software-action: these are how the implementations of methods and programs are described, corresponding to lines of code in a programming language. Each software action has a description, which is a string in the role description.
software-value: these are things that represent a value. This concept overlaps with objects and actions, since a variable and a message can represent a value. Values are important because they represent the data-flow of the system. Every software value has one and only one data type, which is an individual of data-type in the role has-data-type.
code-block: a block of code, either a program or a method. Every code block has an implementation, which is a collection of individuals of software-action in the role has-implementation, and one of these must be identified as the starting action through the role start. A code block may also have local variables associated with it, which are individuals of the concept variable in the role has-local-variables.
data-type: a description of a data type. It may, as discussed in Section 3.5.3, have a superclass, which are other individuals of data-type in the role superclass, and has two parts: slots and methods. The former are individuals of slot in the role has-slots, and the latter are individuals of method in the role has-methods. These two roles are specializations of the role has-parts, so all the methods and slots of a data type will also appear in the has-parts role. A data type is a spanning object, as discussed and illustrated in Section 3.5.3.
data-type-instance: these are the instances of data-types, All instances are also software values, such as constants, parameters, slots, etc., but not all software values are software objects, since a message (which represents a method invocation, q.v.) can also represent a value.
method: a procedure attached to a data-type. A method is a code block, so in addition to the roles associated with code-block, it also may have some special kinds of local variables: parameters, which are individuals of parameter in the role has-parameters, and self variables, which are individuals of self-variable in the role has-self-variable.
program: the top level of the functional decomposition. Generally just ties together the objects and methods that make up the software system. Typically, most of the work will be done by the methods.
constant: an instance of a data-type that cannot be changed, and thus is given an initial value which will remain untouched.
changeable-instance: an instance that can be changed, as opposed to a constant. It is only individuals of this concept (or, more precisely, its subconcepts) that can be used in an assignment statements (q.v.).
create-method: a special kind of method that can be used to instantiate a data type.
destroy-method: a special kind of method that can be used to destroy an instance of a data type.
slot: In the "programming language" which this ontology represents, the only kind of global variable is a slot. This is part of a data type, and a spanning object that maps into a role in the domain model universe. It is a special kind of changeable instance.
variable: A changeable instance that isn't a slot. This might be more appropriately called "local variable," since all the individuals of this concept (and its subconcepts) are local variables, and can only appear as parts of methods.
parameter: the specification of a parameter for a method.
self-variable: a reference within a method to the object that the method is attached to.
action-whole: These are the basic statements available in the programming language this ontology represents. These are the "whole" actions, as some actions have multiple parts, and only whole actions can be used as the implementations of code blocks, ordered with the control flow roles, etc.
action-part: a sub-part of an action whole, such as passing a parameter into a method or a case of a switch.
message: the invocation of a method. In the object-oriented paradigm, procedure invocation is viewed as the process of passing a message to an object. Therefore a message has one and only one data type instance that it is sent to, which is an individual of data-type-instance in the role send-to. It must also identify the method to be invoked, which is one and only one individual of method in the role call-method. If there are parameters to be passed into the method, these must be specified, but in this ontology passing a parameter is represented as an action, a sub-action of message, and these sub-actions are identified as individuals of pass-parameter in the role pass-parameters. There must be one and only one next action after a method.
return: returning from a method, whether it returns a value or not, must be made explicit in this representation. Every branch of every method's control flow must terminate in a return. A return can return a value, which is specified as one and only one individual of software-value in the role return-value. There can be no next actions after a return. Note that since a software value can be a variable, constant, or a message, a return (or any action that uses a software value) can actually invoke another method.
assignment: a change to a changeable instance. The instance to be changed is specified as one and only one individual of changeable-instance in the role changes, and the new value to assign the instance is specified as one and only one individual of software-value in the role new-value. There must be one and only one next action after an assignment.
switch: a branch in the control flow. This is loosely modeled after the switch construct in C. A switch has a value on which it switches, that is specified as one and only one individual of software-value in the role switch-on-value. Each case of the switch is represented as a sub-action, the action of testing the condition of the case, and are specified as two or more individuals of select-switch-case in the role has-switch-cases. There can be two or more next actions after a switch, these are accessed through the switch cases.
pass-parameter: the sub-action of sending a message to an object that passes the parameter in. The value to be passed must be identified as one and only one individual of software-value in the role argument-value. The parameter that this value will be passed in as is specified as one and only one individual of parameter in the role pass-as. If there are multiple parameters to be passed into a method, there are multiple pass parameter actions.
select-switch-case: the sub-action of a switch that actually tests a single case and selects a new control flow path if successful. The value to match for the condition to be true is specified as one and only one individual of software-value in the role case-condition, and the starting whole action of the branch of the control flow selected by this case is specified as one and only one individual of action-whole in the role has-case-action.
The seven types of roles are part-of, control-flow, accessed-by, specialization-of, data-slot, meta-role, and instance-of. The part-of roles relate objects and actions to their parts, for example the parts of a data-type are its slots and method, so slot-of and method-of are roles in the part-of role taxonomy. It should be noted that has-parts is the inverse role to part-of, most cases the inverse of a role is fairly obvious from the name. The inverse of slot-of is has-slots, and the inverse of method-of is has-methods, etc.
The control-flow roles specify the flow of control, as discussed in the previous section.
The specialization-of roles relate a more specific individual to a more general one. For example, the superclass role relates an individual of data-type to another individual of data-type that is more general (it relates a data-type to its superclass). There are three special kinds of specialization links other than superclass. The specialized-method-of role links a method to a more general method. This role is used to prevent inheritance along the superclass hierarchy of a method from a more general class to a more specific one if a more specific version of that method exists at the more specific data-type. This is explained in more detail in the next section, and is also true of the specialized-slot-of role. The third role, interface-method-of, relates a method to one that it is an interface for. This relationship is described in more detail in Chapter 6.
The instance-of roles specify the relationship between a descriptive object, such as a method or data-type, and an instance of that object, such as a variable or message (method invocation). The use of this class of roles is required because this ontology generates another universe of discourse in which methods and data-types are concepts. It should be noted that this is different from the built-in Classic relationship that has been referred to as individual-of.
The data-slot roles are the simplest roles that link individuals of code-level concepts to host individuals. A host individual, as described in Section 2.2.4, refers to built in data such as strings, numbers, etc.
The meta-role roles are used by the user interface, and are described further in later sections.
The accessed-by roles are one of the most useful discovery tools provided by this representation. These roles are all the inverses of the roles used by the code-level actions to specify the objects the actions will work on or with. For example, individuals of assignment have two roles: changes, which identifies the variable or slot to be changed, and new-value which identifies the new value for the variable or slot. The inverse of changes is changed-by, and as a result, every variable and slot automatically has a changed-by link to every assignment statement where it is changed. These roles are discussed in more detail in Chapter 6.
Finally, the roles that are involved in the data-type hierarchy, superclass, has-methods, and has-slots, all have two role subtypes: immediate and inherited. The need for these role types is discussed in the next section.
Most of the rules in the code-level ontology fall into one of four categories: rules for determining decomposition, rules for determining the parts of a code-block, rules for the data-type hierarchy, and rules for describing actions. Aside from the latter type, all rules are specified as paths (see Section 2.2.4.2). The rules for decomposition and describing actions are used predominantly to support discovery, and the rules for determining parts of a code-block are used predominantly to support development. The data-type hierarchy rules present numerous very special circumstances, and will be discussed in this section.
This ontology describes an object-oriented language. An important ingredient in any object-oriented language, as discussed in Section 2.1.5, is inheritance. Inheritance is a very general inference facility that subsumes all forms of natural deduction [Fox, 1979], and the type employed by object-oriented languages is actually a very simple form, called superclass inheritance. Superclass inheritance involves the inheritance of three distinct kinds of information: superclasses, slots, and methods.
The first part of superclass inheritance, inheriting superclasses, is illustrated in Figure 3.7. The solid lines show the told superclass relationships, and the dotted lines show the inherited or inferred links. In this example, since list-of-integers is a subclass of list-of-numbers and list-of-numbers is a subclass of list, we infer that list-of-integers is also a subclass of list.
The second part of superclass inheritance, inheriting slots, is illustrated in
Figure 3.8. In this figure, the solid, unlabeled lines represent the superclass relationship, the labelled lines represent slot definitions, and again dashed lines represent inherited information. In this example, since list-of-people is a subclass of list, it inherits the number-of-elements slot from list. This slot has a restriction that all values must be of the type number. The elements role is not inherited, however, because list-of-people already has a more specialized version of that slot. This is an example of specialization override inheritance, where a value is not inherited if a more special one already fills the role.
The third part of superclass inheritance, inheriting methods, works in precisely the same manner as the inheritance of slots. All methods of a superclass are inherited to the subclass, except those that already exist in the subclass.
Classic, like any knowledge representation language, maintains a facility for supporting superclass inheritance between concepts, which creates a problem for this representation because the classes are individuals of the concept data-type. There is no built-in facility in Classic for superclass inheritance between individuals, so another mechanism must be used to implement this critical feature. With some representation additions, the path facility turns out to be powerful enough to specify this type of inheritance with three rules.
The representation addition is in the form of a sub-type of the three roles involved in the data-type hierarchy (has-methods, has-slots, and superclass). Each of these roles has two subtypes, immediate and inherited (e.g. has-immediate-methods, has-inherited-methods, immediate-superclass, inherited-superclass, etc.). The immediate version of each role links an individual of data-type to the "local" information for that role (this has been illustrated in Figure 3.7 and Figure 3.8 as solid lines), and the inherited version of each role links the data-type to any inherited information (shown as dotted lines in the figures). The "normal" roles combine the immediate and inherited information because they are the parent roles.
The need for the immediate and inherited versions of each role becomes clear when the path facility is considered. Without these special subroles, the rule for e.g. inheriting superclasses would be:
data-type --> superclass (superclass)
data-type --> inherited-superclass (immediate-superclass superclass)
data-type --> has-inherited-methods (immediate-superclass has-methods)
data-type --> has-inherited-slots (immediate-superclass has-slots)
superclass, has-methods, and has-slots roles are filled with all the immediate and inherited fillers. These rules imply an ordering of the inference, as well. The rules won't work for a data-type "lower" in the hierarchy unless they have already fired on the data-types above it. This is taken care of by Classic if the taxonomy is specified, and the roles closed, in the correct order. This requirement is discussed further in Section 4.1.1 and Section 7.1.3.3.
There is still an element missing from the rules for inheriting slots and methods: specialization override. This presents another problem for the Classic representation. The mechanics of specialization override in object-oriented languages are based on the name of the slot or method: if the name is the same, inheritance is blocked. This mechanism is overly simplistic, however. Having the same name does not necessarily imply that the slot or method is more specialized. In this ontology, the relationship must be explicit - for a rule to block inheritance of an object there must be an individual already filling the role that is linked by specialization-of (or any subrole) to the object that should be blocked. This process is illustrated in Figure 3.9, where again dotted lines are used to indicate derived information. The dotted line linking data-type-07 to slot-02 is the only inference resulting from superclass inheritance. The inheritance of slot-01 to data-type-07 is blocked by the fact that slot-03 is a specialized-slot-of slot-01, and specialized-slot-of is a subrole of specialization-of (see Figure 3.6 on page 66). Note also that since has-slots is the parent role of both has-inherited-slots and has-immediate-slots, the (derived) fillers for the has-slots role on data-type-07 are slot-02 and slot-03.
Generated with Harlequin WebMaker