{Begin SubSec Overview} {Title Overview} {Text {Begin SubSec Structure of Classes and Instances} {Title Structure of Classes and Instances} {Text {it Classes:}{index classes} A class is a description of one or more similar objects. An {it instance} is an object described by a particular class. Every object within LOOPS is an instance of exactly one class. Classes themselves are instances of a class, usually the one called {lisp Class}. Classes whose instances are classes are called {it metaclasses}. {it Variables:}{index variables} LOOPS supports two kinds of variables - class variables and instance variables. Class variables are used to contain information shared by all instances of the class. A class variable is typically used for information about a class taken as a whole. Instance variables contain the information specific to an instance. Both kinds of variables have names, values, and other properties. A class describes the structure of its instances by specifying the names and default values of instance variables. For example, the class {lisp Point} might specify two instance variables, {lisp x} and {lisp y} with default values of {lisp 0}, and a class variable, {lisp lastSelectedPoint}, used by methods associated with all instances of class {lisp Point}. LOOPS also allows "variable length" classes, which have some instance variables that are referenced by numerical index. {it Methods:}{index methods} A class specifies the behavior of its instances in terms of their response to {it messages}. The class associates {it selectors}{index selectors} (LISP atoms) with {it methods}, the Interlisp functions that respond to the messages. All instances of a class use the same selectors and methods. Any difference in response by two instances of the same class is determined by a difference in the values of their instance variables. For example, {lisp PrintOn} is used as a selector for the message which knows how to print out a representation of an object on a file. {it Properties:}{index properties} LOOPS provides user-extendible property lists for classes, their variables, and their methods. Property lists provide places for storing documentation and additional kinds of information. A property list on a variable is used to store additional information about both the variable and its value. For example, in a knowledge engineering application, a property list for an instance variable could be used to store such information as {it support} (i.e., reasons for believing a value), {it certainty factors} (i.e., numeric assessments of degree of belief), {it constraints} on values, {it dependencies} (i.e., relationships to other variables), and {it histories} (i.e., previous values). {it Metaclasses:}{index metaclasses} Classes themselves are instances of some class. When we want to distinguish classes whose instances are classes, we call them metaclasses, after the Smalltalk usage. When a class is sent a message, its metaclass determines the response. For example, instances of a class are created by sending the class the message {lisp New}. For most classes, this method is provided by the standard metaclass for classes: {lisp Class}. The user can create other metaclasses to perform specialized initialization. The metaclass for {lisp Class} itself (called {lisp MetaClass}) contains the {lisp New} method for making classes. Another useful metaclass provided in the system is {lisp AbstractClass}. It is used for classes that are placeholders in the inheritance network that it would not make sense to instantiate. Its response to a {lisp New} message is to cause an error. {Begin Figure} {Tag DEFCLASSAreaBudget} {Text {lispcode [DEFCLASS AreaBudget (MetaClass Class EditedBy (* dgb "15-Feb-82 14:32 ") doc (* * This is a sample class chosen to illustrate the syntax of classes in LOOPS. Commentary on the class is inserted in a standard property in the class. -- e.g. Budgets are ...)) (Supers OwnedObject Budget) (ClassVariables (maxBase 25000)) (InstanceVariables (owner #$VLSI doc (* organizational area that owns budget) ) (base 1000 doc (* The initial amount of money)) (overhead 2.25 doc (* Multiplied by base to get total.)) (employees NIL doc (* list of employees in this area)) (manager NIL doc (* manager of this area)) (total #(SHARED getTotal UpdateNotAllowed) doc (* value of total is computed using active value.)) (Methods (Report AreaBudget.Report doc (* Prints out a budget report)) (StoreBase AreaBudget.StoreBase doc (* store base value checking maxBase))]} } {Caption Example of a class definition in LOOPS. The class, called {lisp AreaBudget}, inherits variables and methods from both of its super classes ({lisp OwnedObject} and {lisp Budget}). The form of the definition here does not show inherited information, only the changes and additions. In this example the new class variable {lisp maxBase} is introduced, and six instance variables ({lisp owner}, {lisp base}, {lisp overhead}, {lisp employees}, {lisp manager}, and {lisp total}) are defined. The {lisp Methods} declaration names the Interlisp functions that implement the methods. For example, {lisp AreaBudget.Report} is the name of a function that implements the {lisp Report} method for instances of {lisp AreaBudget}. } {End Figure} }{End SubSec Structure of Classes and Instances} {Begin SubSec Inheriting Variables and Methods} {Title Inheriting Variables and Methods} {Text Inheritance is an important tool for organizing information in objects. It enables the easy creation of objects that are "almost like" other objects with a few incremental changes. Inheritance avoids the user have to specify redundant information and simplifies updating, since information that is common need be changed in only one place. LOOPS objects exist in an {it inheritance network}{index inheritance networks} of classes. An object inherits its instance variable description and message responses. All descriptions in a class are inherited by a subclass unless overridden in the subclass. For methods and class variables, this is implemented by a runtime search for the information, looking first in the class, and then at the super classes specified by its {it supers list}.{index supers list} For instance variables, no search is made at run time; default values are cached in the class, and are updated if any super is changed, thus maintaining the same semantics as the search. Each class can specify inheritance of structure and behavior from any number of super classes in its supers list. {it Hierarchy:} In the simplest case, each class specifies only one super class. If the class {lisp A} has the supers list {lisp (B)}, a one element list containing {lisp B}, then all of the instance variables specified local to {lisp A} are added to those specified for {lisp B}, recursively. That is, {lisp A} gets all those instance variables described in {lisp B} and all of {lisp B}'s supers. In this case one obtains strict inheritance hierarchy as in Smalltalk. Any conflict of variable names is resolved by using the description closer to {lisp A} in traversing up the hierarchy to its root at the class {lisp Object}. Method lookup uses the same conflict resolution. The method to respond to a message is obtained by first searching in {lisp B}, and then searching recursively in {lisp B}'s supers list. An example of this is given in {FigureRef ABinheritance}. {Begin Figure} {Tag ABinheritance} {Text {Begin Table} {COLUMN} {COLUMN} {COLUMN} {COLUMN} {First Class} {Next Super} {Next InstanceVariables} {Next Methods} {First {lisp Object}} {Next {lisp NIL}} {Next none} {Next {lisp (s4 M6)}} {First {lisp C}} {Next {lisp Object}} {Next {lisp (w 7)}} {Next {lisp (s2 M4) (s3 M5)}} {First {lisp B}} {Next {lisp C}} {Next {lisp (y 4) (z 3)}} {Next {lisp (s1 M2) (s2 M3)}} {First {lisp A}} {Next {lisp B}} {Next {lisp (x 1) (y 0)}} {Next {lisp (s1 M1)}} {End Table} } {Caption In the definitions given in the above chart, an instance of {lisp A} would be given four instance variables, {lisp w}, {lisp y}, {lisp z}, and {lisp x} in that order. The default value for {lisp y} would be {lisp 0}, which overrides the default value of {lisp y} inherited from {lisp B}. The instance would also respond to the four messages with selector {lisp s1}, {lisp s2}, {lisp s3}, and {lisp s4}. The method used for responding to {lisp s1} is {lisp M1}, which is said to override {lisp M2} as the implementation of the message {lisp s1}. Similarly, {lisp M3} overrides {lisp M4} as the implementation of message {lisp s2}. Notice that the root class in the system, {lisp Object}, has no super class. All classes in the system are subclasses of {lisp Object}, directly or indirectly. } {End Figure} {it Multiple Super Classes:} Classes in LOOPS can have more than one class specified on their supers list. Multiple super classes admit a modular programming style where (i) methods and associated variables for implementing a particular feature are placed in a single class and (ii) objects requiring combinations of independent features inherit them from multiple supers. If {lisp D} had the supers list {lisp (E A)}, first the description from {lisp E} and its supers would be inherited, and then the description from {lisp A} and its supers. In the simplest usage, the different features have unique variable names and selectors in each super. In case of a name conflict, LOOPS uses a depth-first left to right precedence. For example, if any super of {lisp E} had a method for {lisp s1}, then it would be used instead of the method {lisp M1} from {lisp A}. In every case, inheritance from {lisp Object} (or any other "common" super class) is only considered after all other classes on the recursively defined supers list. }{End SubSec Inheriting Variables and Methods} {Begin SubSec Data Oriented Programming -- Using Active Values} {Title Data Oriented Programming -- Using Active Values} {Text {index active values} In data oriented programming, one needs a way of specifying for any variable of an object whether any special procedure is to be invoked on read or write access, and if so which. In LOOPS we check on every variable access whether the value is marked as an {it active value}. If so, the active value specifies the procedures to be invoked when the value of a variable (or property) is read or set. This mechanism is dual to the notion of messages; messages are a way of telling objects to perform operations, which can change their variables as a side effect; active values are a way of accessing variables, which can send messages as a side effect. The following notation for active values illustrates its three parts: {lispcode #({arg localState} {arg getFn} {arg putFn})} This notation is converted by a read macro into an instance of the LISP data type {lisp activeValue}. The {arg localState} is a place for storing data. The {arg getFn} and {arg putFn} are the names of functions that are applied with standard arguments when a program tries to get or put the value of a variable. Every active value need not specify both a {arg getFn} and a {arg putFn}. If the {arg getFn} is {lisp NIL}, then a get operation returns the local state. If the {arg putFn} is {lisp NIL}, then a put operation replaces the local state. Active values enable one process to monitor another one. For example, we have developed a LOOPS debugging package that uses active values to trace and trap references to particular variables. Another example is a graphics package that updates views of particular objects on a display when their variables are changed. In both cases, the monitoring process is invisible to and isolated from the monitored process. No changes to the code of the monitored object are necessary to enable monitoring. {it Model/View Controller Example:} {FigureRef ModelViewExample} shows an application of this to a simulation model. Suppose that we want a program that simulates the flow of traffic in a city and displays selected parts of the simulation on a screen. Active values enable us to divide the programming of this example into two parts: the traffic model and the view controller. The traffic model consists of objects representing automobiles, traffic lights, emergency vehicles, and so on. These objects exchange messages to simulate traffic interactions (e.g., when a traffic light turns green, it would send {lisp Move} messages to start cars moving). The view controller provides windows into different parts of the city. It contains information about how the objects are to be displayed. We want a user to be able to move these windows around to change the view. {Begin Figure} {Tag ModelViewExample} {Text {lispcode (DEFINST Automobile-1 ... (InstanceVariables (position #(Pos1 NIL UpdateDisplay) displayObjects (DispObj1 DispObj2 DispObj3) doc (* position of car in traffic coordinate system)) (speed 25)) ...]} } {Caption Instance of an automobile in a traffic simulation model. Other classes describe such things as traffic lights, city blocks, and emergency vehicles. Instances of these classes exchange messages while simulating the vehicles moving around in the model. The instance variable {lisp position} is used to record the location of an automobile in the traffic coordinate system. In this example, an active value in {lisp position} is used to update view objects that control pictures of the traffic patterns on an interactive display. Whenever a simulation method puts a new value into the {lisp position} variable, the procedure {lisp UpdateDisplay} sends update messages to each object in a list of view objects. These messages ultimately cause the graphics display to be updated. } {End Figure} In {FigureRef ModelViewExample}, there is an active value in the {lisp position} variable of an instance of {lisp Automobile}. This active value is the interface between the object in the simulation model and the view controller. Whenever a method in the simulation model changes the value of a {lisp position} variable, the procedure {lisp UpdateDisplay} in the {arg putFn} of the active value is invoked. {lisp UpdateDisplay} updates the local value and sends a message to each of the view objects in the list stored as a property of {lisp position}. These objects respond to a message by updating the view in the windows on the display screen. The important point of this example is that it shows how the view controller can be invoked as a side effect of running the simulation. The view can be changed without effecting any programs in the simulation model. To change the set of simulation objects being monitored, only the interface to the view controller needs to be changed by adding active values. The objects in the view controller may also be changed (e.g., to reflect changes to relative coordinates of the window and the traffic model). }{End SubSec Data Oriented Programming -- Using Active Values} {Begin SubSec Knowledge Bases} {Title Knowledge Bases} {Text {index knowledge bases} LOOPS was created to support a design environment in which there are community knowledge bases that people share, and to which they can add incremental updates. We have chosen the term {it knowledge base} instead of {it data base} to emphasize the intended application of LOOPS to expert systems. In expert systems, knowledge bases contain inference rules and heuristics for guiding problem solving. This is in contrast to the tabular files of facts usually associated with data bases. {it Knowledge Bases:} Knowledge bases in LOOPS are files that are built up as a sequence of layers, where each layer contains changes to the information in previous layers. A user can choose to get the most recent version of a knowledge base (that is, all of the layers) or any subset of layers. The second option offers the flexibility of being able to share a community knowledge base without necessarily incorporating the most recent changes. It also provides the capability of referring to or restoring any earlier version. {FigureRef 123KnowledgeBase} illustrates this with an example. {Begin Figure} {Tag 123KnowledgeBase} {Text {lispcode ------------------------- Layer 1 ------------------------- Obj1 (x 4) ... Obj2 (y 5) (w 3) ... ------------------------- Layer 2 ------------------------- Obj2 (y 7) (w 2) ... Obj3 (z 6) ... ------------------------- Layer 3 ------------------------- Obj1 (x 8) ... Obj4 (z 9) ...} } {Caption Knowledge bases in LOOPS are files that are built-up incrementally as a sequence of layers. Each layer contains updated descriptions of objects. When a knowledge base is opened, the information in the later layers overrides the information in the earlier layers. LOOPS makes it possible to select which layers will be used when a knowledge base is opened. In this example, if the knowledge base is opened and only the first 2 layers are used, then {lisp Obj1} will have an {lisp x} variable with value {lisp 4}. If all three layers were connected, then the value would be {lisp 8}. } {End Figure} {it Community Knowledge Bases:} LOOPS partitions the process of updating a community knowledge base into two steps. Any user of a community knowledge base can make tentative changes to a community knowledge base in his own (isolated) environment. These changes can be saved in a layer of his personal knowledge base, and are marked as associated with the community knowledge base. In a separate step, a data base manager can later copy such layers into a community knowledge base. This separation of tasks is intended to encourage experimentation with proposed changes. It separates the responsibility for exploring possibilities from the responsibility of maintaining consistent and standardized knowledge bases for shared use by a community. The same mechanisms can be used by two individuals using personal knowledge bases to work on the same design. They can conveniently exchange and compare layers that update portions of a design. {it Unique Identifiers:} The ability to determine when different layers are referring to the same entity is critical to the ability to share data bases. To support this feature the LOOPS data base assigns unique identifiers (based on the computer's identification numbers, the date, and an unbounded count) to objects before they are written to a knowledge base. This facility provides a grounding for more sophisticated notions of equality that might be desired in knowledge representation languages built on LOOPS. {it Environments:}{index environments} A user of LOOPS works in a personalized {it environment}. An environment provides a lookup table that associates unique identifiers with objects in the connected knowledge bases. In an environment, user indicate dominance relationships between selected knowledge bases. When an object is referenced through its unique identifier, the dominance relationships determine the order in which knowledge bases are examined to resolve the reference. By making personal knowledge bases dominate over community knowledge bases, a user can override portions of community knowledge bases with his own knowledge bases. {it Multiple Alternatives:}{index multiple alternatives} An important use of environments is for providing speedy access to alternative versions (e.g., multiple alternatives in a design). A user can have any number of environments available at the same time. Each environment is fully isolated from the others. Operations that move information between environments are always done explicitly through knowledge bases. }{End SubSec Knowledge Bases} }{End SubSec Overview}