{Begin SubSec Using Several Knowledge Bases in an Environment} {Title Using Several Knowledge Bases in an Environment} {Text The partitioning of knowledge into multiple knowledge bases can be a useful tool for organizing knowledge. For example, long term storage of different versions of a design can be kept in separate knowledge bases in Loops. (The different knowledge bases in these cases correspond to different environments.) It is also convenient to partition knowledge bases to reflect the partitioning of responsibility for setting standards and maintaining consistency. The previous scenarios have shown the use of separate knowledge bases to keep (tentative, idiosyncratic) personal knowledge separate from (open, standardized) community knowledge. This scenario shows how a user can access several knowledge bases through a personal knowledge base. The first step is to open the personal knowledge base as follows: {lispcode (← $KB Old '{arg KBName} '{arg environmentName})} {index Old (Method of KBMeta)} The next step is to add all of the other knowledge bases that the user wants as follows: {lispcode (← ${arg KBName} AddToContents '{arg otherKBName{sub 1}}) (← ${arg KBName} AddToContents '{arg otherKBName{sub 2}}) (← ${arg KBName} AddToContents '{arg otherKBName{sub 3}}) ...} {index AddToContents (Method of KB)} This can be repeated for each knowledge base to be added. Each {lisp AddToContents} message changes the {lisp contents} variable of the knowledge base named {arg KBName} so that it now refers indirectly to the other KBName. These references are preserved across sessions so that the next time the user opens his knowledge base with an {lisp Old} message, he will not need to repeat the {lisp AddToContents} messages. These references can be removed as in the previous session. For most applications, the order in which knowledge bases are added does not matter. However, if an object reference is ambiguous in the sense that the object is contained in more than one of the knowledge bases, then the last knowledge base added will dominate. After the knowledge bases have been added, the user can optionally freeze the references to any of them as described earlier. The next step is to open an environment: {lispcode (← ${arg environmentName} Open)} {index Open (Method of Environment)} As the user creates new objects in his environment, he could want them to be associated with particular knowledge bases that he is using. Usually, he will want them associated with his personal knowledge base (named {arg KBName} in the example), and this is the default association. However, bugs in a community knowledge base will often be found by a user working on an example in a personal knowledge base. If the user simply changes the buggy objects, they will continue to be associated with the community knowledge base when he saves them at the end of his session. However, if he creates new objects that he wants associated with the community knowledge base, he can first type: {lispcode (← ${arg environmentName} AssocKB '{arg otherKBName{sub 1}})} {index AssocKB (Method of Environment)} This message first checks that there is a knowledge base named {arg otherKBName{sub 1}} in the environment. It does not cause the changes to be written to the other knowledge bases. Rather, it causes a specially marked layer to be created in the user's personal knowledge base which can be accessed later by the community knowledge base manager. The user can then create the new objects. When he is done creating these objects, he can then switch the association back to his personal knowledge base by typing: {lispcode (← ${arg environmentName} AssocKB '{arg KBName})} {index AssocKB (Method of Environment)} As before, the user can type {lispcode (← ${arg environmentName} Close)} {index Close (Method of Environment)} when he is done with the session. Occasionally, a user may accidentally associate some objects with the wrong knowledge base. See the next section for a way to change the association of an object after it has been created. If he later resumes the session, he will have access to all of the knowledge bases that he added. }{End SubSec Using Several Knowledge Bases in an Environment} {Begin SubSec Changing the Associations of Objects} {Title Changing the Associations of Objects} {Text The previous scenario depends on anticipating a change in the intended association of an object before creating it. This approach using an {lisp AssocKB} message works fine if the creation of objects can be conveniently organized into periods such that all of the objects created during a period are associated with the same knowledge base. In practice, however, a user may forget to send the message or he may later change his mind about the appropriate association for an object. The message for changing the association of an object is the {lisp AssocKB} message as follows: {lispcode (← ${arg objectName} AssocKB '{arg newKBName})} {index AssocKB (Method of Environment)} }{End SubSec Changing the Associations of Objects} {Begin SubSec Switching Among Environments} {Title Switching Among Environments} {Text One of the important features of Environments is that they provide a way of having independent versions of designs. A user can have several open Environments and can switch between them by making one of them the "current" Environment. In this scenario, we will first consider two ways that a user can create multiple open Environments. Then we will consider how to switch among them and how to copy objects between them. {it Case 1.} In this case, a user is just starting a session. He has a personal knowledge base named {lisp KBName1}, and he wants to create two knowledge bases ({lisp KBName2} and {lisp KBName3}) to represent two versions of a design. To do this, the user can type: {lispcode (← $KB New 'KBName2 '{arg environmentName2}) {it Create 2nd knowledge base and Environment.} (← $KB New 'KBName3 '{arg environmentName3}) {it Create 3rd knowledge base and Environment.} (← $KBName2 AddToContents 'KBName1) {it Add KBName1 to the contents of 2nd KB.} (← $KBName3 AddToContents 'KBName1) {it Add KBName1 to the contents of 3rd KB.} (← ${arg environmentName2} Open) {it Open the 2nd Environment.} (← ${arg environmentName3} Open) {it Open the 3rd Environment, leaving it as current.}} {index New (Method of KBMeta)} {index AddToContents (Method of KB)} {index Open (Method of Environment)} {it Case 2.} Alternatively, the user may discover part way through a session that he wants to branch out with another Environment. In this scenario, the user is working in Environment1 and decides to create a branch point. Before doing this, the user must first Close that environment: {lispcode (← ${arg environmentName1} Close)} {index Close (Method of Environment)} The user can then create the Environment2 and Environment3 as in case 1. {it Switching.} In both cases, the last Environment opened will be the default current one. The user can make any Environment be current by: {lispcode (← ${arg environmentName2} MakeCurrent)} {index MakeCurrent (Method of Environment)} All Loops operations will then happen in this Environment. To switch to {arg environmentName3} use: {lispcode (← ${arg environmentName3} MakeCurrent)} and so on. To test whether any particular environment, {arg testedEnvironment} is current, one uses: {lispcode (← ${arg testedEnvironment} IsCurrent)} {index IsCurrent (Method of Environment)} To switch to the GlobalEnvironment, one sends to the current environments: {lispcode (← CurrentEnvironment MakeNotCurrent)} {index MakeNotCurrent (Method of Environment)} The Lisp global variable {var CurrentEnvironment}{index CurrentEnvironment Var} is bound to the environment which is current. When done, the updates should be written out for all of the open Environments. This can be done by sending {lisp Cleanup} or {lisp Close} messages to each of the environment, or can be done by sending the corresponding message to the class Environment which will send the message on to each open environment (kept on a list in the Lisp global variable {var openEnvironments}):{index openEnvironments Var} {lispcode (← $Environment Cleanup) (← $Environment Close)} {index Cleanup (Method of EnvironmentMeta)} {index Close (Method of EnvironmentMeta)} {it Copying Objects between Environments.} While a user is switching between environments, he may make discover an error in some information that is global to both environments. In this scenario, the user discovers an error in some objects from a community knowledge base while he is working in Environment2. He corrects the objects in Environment2, and wants to copy those corrections into Environment3. He does this using the {lisp CopyObjects} message as follows: {lispcode (← ${arg toEnvironment} CopyObjects {arg objectsList})} {index CopyObjects (Method of Environment)} where {arg toEnvironment} is the name of the environment that the objects are copied to, and {arg objectsList} is a list of objects to be copied. This message causes the objects to be copied. If the objects already exist in the {arg toEnvironment}, then the copies overwrite the previous objects. In our scenario, the user would perform the following steps: {lispcode (← ${arg environmentName2} MakeCurrent) {it Make Environment2 current.} ... {it Collect the objects.} (SETQ objectsList ...) {it Make a list of the collected objects.} (← ${arg environmentName3} CopyObjects objectList) {it Copy the objects to Environment3.}} {index MakeCurrent (Method of Environment)} {index CopyObjects (Method of Environment)} }{End SubSec Switching Among Environments} {Begin SubSec Saving Parts of a Session} {Title Saving Parts of a Session} {Text {it Saving part of a session.} To selectively update the knowledge base with some of the changes that he made in a session, a user can send a {lisp Cleanup} message to his Environment with KBs specified. For example, to save the updates associated only with the knowledge bases named {lisp KBName1} and {lisp KBName2}, he can send the message: {lispcode (← ${arg environmentName} Cleanup '(KBName1 KBName2))} {index Cleanup (Method of Environment)} This message writes out file layers to the user's personal knowledge base containing the objects that from the current Environment that are associated with the knowledge base {lisp KBName1} and {lisp KBName2}. The user has omitted the names of associated knowledge bases for which he wants to discard the changes. This message completes by writing out the boot layer. The {lisp Cleanup} message without KB's specified writes a layer for every associated knowledge base that has been changed, followed by a {lisp WriteBoot}. If the user does a {lisp (← ${arg envName} Cleanup T)}, then all the changes will be written out in a single layer associated with the connected knowledge base. {it Cancelling an entire session.} The previous scenarios assumed that a user wanted to save the changes that he makes in a session. Sometimes, however, a user may prefer to discard the changes that he has made in a session. He can do this and return the environment to an unopened state by typing: {lispcode (← ${arg environmentName} Cancel)} {index Cancel (Method of Environment)} Cancelling this session will not go back past the last time the user did a {lisp Cleanup}. {lisp Cancel} backs up changes made since that time and then does what a {lisp Close} would do, destroying objects in the environment, and closing files. }{End SubSec Saving Parts of a Session} {Begin SubSec Copying Layers from one Knowledge Base to Another} {Title Copying Layers from one Knowledge Base to Another} {Text The ability to describe layers using a KBState makes it possible for one knowledge base to indirectly access the file layers of another one. This mechanism works fine when it is used to extend a personal knowledge base to include a community knowledge base. It enables several users to read a community knowledge base at the same time and to write their updates to their personal knowledge bases. However, the indirection mechanism breaks down if some users want to read a knowledge base while another user is writing to it. For example, such a conflict could arise if a community knowledge base used the indirection mechanism to access a file layer in some personal knowledge base. Whenever the owner of the personal knowledge base was updating it, users of the community knowledge base would be blocked by the file system. To avoid such situations, it is necessary to create community knowledge bases that physically contain all of the file layers that they reference. In this scenario, the user is just starting a session and no knowledge bases have been opened. The user wants to copy information from a knowledge base named {arg fromKBName} to a knowledge base named {arg toKBName}. The first step is to read the boot layers of the two knowledge bases. {lispcode (← $KB Old '{arg fromKBName}) (← $KB Old '{arg toKBName})} {index Old (Method of KBMeta)} In this scenario, one need not, and in fact should not, have an envrionment open or either of the two KBs connected to an environment. All the work will go on in the Global Environemnt. The second step is to create a description of the layers to be moved. This description can be either a Layer or a KBState. One way to create this description is to use any of the object editors available in Loops. Another way is to send a {lisp DescribeLayers} message as follows: {lispcode (← ${arg fromKBName} DescribeLayers {arg DateOrDays} {arg associatedKB})} {index DescribeLayers (Method of KBState)} {arg DateOrDays} can be an Interlisp Date or an integer number of days. If it is a date, then only those Layers created on or after the given date will be described. If it is an integer, then only Layers created within that many days will be described. If it is {lisp NIL}, then no date filter will be applied. {arg associatedKB} is the name of the knowledge base with which the Layers are associated. (If {lisp NIL}, then the layers associated with any knowledge base will be described.) For example: {lispcode (SETQ layerDescription (← ${arg fromKBName} DescribeLayers 14 '{arg toKBName}))} returns a KBState describing the Layers created in the last fourteen days in the knowledge base named {arg fromKBName} that are associated with the knowledge base named {arg toKBName}. Given such a description, the layers can be copied by typing: {lispcode (← ${arg toKBName} CopyFileLayers layerDescription)} {index CopyFileLayers (Method of KB)} }{End SubSec Copying Layers from one Knowledge Base to Another} {Begin SubSec Summarizing and Combining Knowledge Bases} {Title Summarizing and Combining Knowledge Bases} {Text {it Summarizing a Knowledge Base.} As knowledge bases evolve over time, the number of layers and amount of overridden information can consume a large fraction of the file space. Economy-minded knowledge base managers may want to create "compressed" versions of knowledge bases that have all of the information contained in just one layer. In this scenario, the user starts a session by typing: {lispcode (← $KB Summarize {arg fromKBName} {arg toKBName} {arg assocKBNames})} {index Summarize (Method of KBMeta)} where {arg fromKBName} is the knowledge base to be summarized; {arg toKBName} is the knowledge base to be created. It must be a different name than {arg fromKBName}; {arg assocKBNames} must be a list of KBNames or {lisp NIL}. If it is list, then all, and only those objects with associated KB's on the list will be dumped to the file. One must include {arg fromKBName} on {arg assocKBNames} if changes and objects associated with it are to be dumped to the file. If {arg assocKBNames} = {lisp NIL}, all objects on the file will be dumped on a single layer if {arg toKBName}.{note ??} This message causes Loops to read the boot layer of the old knowledge base ({arg fromKBName}), create a new knowledge base ({arg toKBName}), create an Environment associated with the new knowledge base, read in all of the objects in {arg fromKBName}, write them out to a single layer, and then write a boot layer for the new knowledge base. {it Combining Knowledge Bases.} The {lisp Summarize} message can also be used to combine several existing knowledge bases into a single new knowledge base. In this case, the message is as follows: {lispcode (← $KB Summarize {arg fromKBNames} {arg toKBName} {arg assocKBNames})} {index Summarize (Method of KBMeta)} where {arg fromKBNames} is a list of the names of the knowledge bases to be summarized; {arg toKBName} is the name of the new knowledge base to be created; {arg assocKBNames} is as described above. This message causes Loops to read the boot layers of the old knowledge bases, creates a new knowledge base ({arg toKBName}), creates an Environment associated with the new knowledge base, reads in all of the objects, writes them out to a single layer, and then writes a boot layer for the new knowledge base. The user can create a new knowledge base which contains all of the objects in any open environment. This may include objects from any number of KB's. {lispcode (← {arg environment} DumpToKB {arg toKBName} {arg assocKBNames})} {index DumpToKB (Method of Environment)} will create a new KB named {arg toKBName}, and dump from the environment all objects with associated KB on the list {arg assocKBNames} onto {arg toKBName} (or all objects if {arg assocKBNames} = {lisp NIL}). }{End SubSec Summarizing and Combining Knowledge Bases} {Begin SubSec Subdividing a Knowledge Base} {Title Subdividing a Knowledge Base} {Text Sometimes a user may want to subdivide a knowledge base so that a subset of the objects are moved away to create a new knowledge base. In our scenario, the user wants to move the objects from a knowledge base in {arg fromEnvironmentName} to a knowledge base ({arg toKBName}) included in {arg toEnvironmentName}. In the first step of this scenario the user uses the {lisp MapObjectNames} message: {lispcode (← ${arg environmentName} MapObjectNames (FUNCTION {arg UserFn}) {arg AssocKBs} {arg NoUIDs})} {index MapObjectNames (Method of Environment)} where {arg UserFn} is a function that will be applied to every object name. If {lisp NIL}, then a list of object names and UIDs in environment is returned as the value of the message. If it is the atom {lisp T}, then only names which are not UIDs will be returned. {arg AssocKBs} is an optional argument. If an atom, it is interpreted as the name of the associated knowledge base for the objects. If a list, will be interpreted as a list of associated knowledge bases for the object. If {lisp NIL}, only objects associated with the current AssocKB of the Environment will be used. If {arg NoUIDs} is {lisp T}, then {arg UserFn} will only be applied to real names, and not UIDs. In our scenario, we will assume that {lisp MyFn} will create a list of the objects ({lisp objectList}) that the user wants to move. The user switches to the source environment, finds the objects and moves them: {lispcode (← ${arg fromEnvironmentName} MakeCurrent) {it Switch to fromEnvrionment.} (← ${arg fromEnvironmentName} MapObjectNames (FUNCTION MyFn)) {it Make list of objects.}} {index MakeCurrent (Method of Environment)} {index MapObjectNames (Method of Environment)} The next step is to move the objects as follows: {lispcode (SETQ newObjectList (← ${arg toEnvironmentName} MoveObjects objectList)} {index MoveObjects (Message)} This causes the objects to be copied to toEnvironment and deleted from fromEnvironment (or whatever Environment they came from). The objects will continue to be associated with whatever AssocKB they were before. In this scenario, however, the user wishes them be associated with the knowledge base named toKBName. {lispcode (← ${arg fromEnvironmentName} MakeCurrent) (for object in newObjectList do (← object AssocKB 'toKBName)} {index MakeCurrent (Method of Environment)} {index AssocKB (Method of Environment)} The final step is to write out the changes: {lispcode (← ${arg environmentName} Cleanup)} {index Cleanup (Method of Environment)} }{End SubSec Subdividing a Knowledge Base} {Begin SubSec Going Back to a Previous Boot Layer of a Knowledge Base} {Title Going Back to a Previous Boot Layer of a Knowledge Base} {Text Since knowledge bases are represented as objects, it is possible to reconfigure their contents using the standard object access functions. However if a Layer has been deleted from the contents of a KB, that layer is no longer written out to the boot layer. This can make it difficult to get back to versions modified in this way. The following message makes it possible restore such knowledge bases by reading in old boot layers: {lispcode (← $KB ReadOldBootLayer '{arg KBName} {arg numberBack})} {index ReadOldBootLayer (Method of KBMeta)} The value returned is a KB which has the name KBName, and the state corresponding to the boot layer specified. To preserve a KBState which has these contents, the user can then use: {lispcode (← ${arg KBName} Copy)} {index Copy (Method of KB)} }{End SubSec Going Back to a Previous Boot Layer of a Knowledge Base} {Begin SubSec Affecting what is Saved} {Title Affecting what is Saved} {Text The user may not wish an object, or some part of an object saved on a knowledge base. In this section, we describe a number of ways of stopping information from being written on the knowledge base, with appropriate caveats for the use of these features. {Begin SubSec Temporary Objects} {Title Temporary Objects} {Text If the user is creating lots of objects for temporary use (as intermediate products of a computation) then none of those objects are useful after the computation is done. To create such objects, the user should use: {lispcode (← {arg class} NewTemp)} {index NewTemp (Message)} to create them instead of the usual {lisp (← {arg class} New)} message. Objects created in this way will not be given a UID, and will be not be accessible by mapping through the environment. If by some chance they are referenced from some object that is being dumped to the data base, they will then be converted into permanent objects, and dumped to that same KB. }{End SubSec Temporary Objects} {Begin SubSec Not Saving some IV values} {Title Not Saving some IV values} {Text For some instances, it is useful to store in an instance variable a Lisp dataytpe (e.g. a pointer to a window, or hash array). However, most Lisp datatypes are not stored appropriately on a KB. In general, when read back in from a KB, what was formerly an instance of a datatype looks like an atom with a funny printname. The solution we have adopted is to allow the user to specify IV values or properties which should not be dumped to a knowledge base. When read back in, the IV value or property will inherit the default value from the class which can be an active value to recreate the desired Lisp object. For example, the class {lisp $Environment} uses a hash table as the value of its IV nameTable. The following fragment of the definition of Environment shows how saving the value of {lisp nameTable} is suppressed and how an active value is used to recreate it. {lispcode [DEFCLASS Environment ... (InstanceVariables ... (nameTable #(NIL NewNameTable) DontSave Any) ...]} Any instance of environment will have {lisp nameTable} filled in by {lisp NewNameTable} the first time it is accessed. {lisp NewNameTable} is a specialized version of {lisp FirstFetch} which makes the local value be a hashArray. The property {lisp DontSave} with value {lisp Any} (which is inherited in every instance) specifies that nothing about the IV {lisp nameTable} should be saved on a KB. For finer control, the property {lisp DontSave} could have been given a value which is a list of property names whose values should not be saved on the KB. If the atom {lisp Value} is included in the list, then the value of the IV itself will not be saved. The value {lisp Any} for {lisp DontSave} is interpreted as meaning no porperty or value should be saved. }{End SubSec Not Saving some IV values} {Begin SubSec Ignoring changes on an IV} {Title Ignoring changes on an IV} {Text Whenever an object is modified during the course of a session, it is marked as changed so that a new version of the object will be written out on the KB. Suppose the user may be using an IV globally known object as a place to cache some information. In this case the user does not need or even want the known object to be marked as changed if the only change made was to store the cached information. To allow this, the special active value function {fn StoreUnmarked} is provided which does not mark the object as changed when it updates its localState. For example, if {lisp $WorldView} had an instance variable {lisp lastSelected} which was updated each time a selection was made, then if {lisp $WorldView} looked like: {lispcode [DEFINST WorldView ... (lastSelected #(obj1 NIL StoreUnmarked) ...]} changes to {lisp lastSelected} would be ignored by the KB system. It is often useful to combine this feature with {lisp DontSave} described earlier so that when the object is dumped to a KB (because of some other change) the value in this IV is not saved. Then the {lisp activeValue} can be inherited directly from the default value in the class. Using {lisp DontSave} by itself is not sufficient to ensure that the object will not be dumped if a value is changed in the not to be saved IV. }{End SubSec Ignoring changes on an IV} {Begin SubSec Getting rid of objects explicitly} {Title Getting rid of objects explicitly} {Text During the course of a session users may create a number of objects they discover before the end of the session are not needed. They may also decide that some old objects are no longer needed. By using: {lispcode (← {arg obj} Destroy)} {index Destroy (Message)} for each such object, the user will cause any new objects to be forgotten (not written to the KB) and the incore space reclaimed. For objects which were in the KB previously, there will be stored an indication that this object has been deleted, so that later reading of this KB will not contain the object. }{End SubSec Getting rid of objects explicitly} }{End SubSec Affecting what is Saved} {Begin SubSec Examining Environmental Objects} {Title Examining Environmental Objects} {Text Sending the message {lisp MapObjectNames} to an open environment allows one access to the names and UIDs of objects in that environment. From the names and UIDs one can then access the objects themselves using {lisp GetObjectRec}. One can determine the names and UIDs of objects in a Layer by sending that layer the message {lisp MapObjectNames}. The form is: {lispcode (← ${arg Layer1} MapObjectNames {arg mapFn} {arg noUIDs})} {index MapObjectNames (Method of Layer)} which applies {arg mapFn} to each name (and to each UID unless {arg noUIDs}={lisp T}). If {arg mapFn}={lisp NIL} then this simply returns a list of the names (and UIDs). However, unless the layer has been read in to an environment, one cannot get the object associated with that name (UID) on that layer. {it PrettyPrinting a KB:} A special pretty printing function is available for KB's, KBStates, and Layers which tell about its history and contents. If one does: {lispcode (← $KB Old '{arg KBName})} without necessarily opening an environment, then one can send: {lispcode (← ${arg KBName} PP)} {index PP (Message)} to see what is in the KB and its containing layers. {it ChangedKBs:} In a particular environment, one can change objects which originate on any number of community and personal knowledge bases. To find out the names of any KBs that have modified entities associated with them, one send to that environment, say {lisp E1}: {lispcode (← $E1 ChangedKBs)} {index ChangedKBs (Method of Environment)} It is this list which is used by {lisp Cleanup} to determine the set of layers that will be dumped at cleanup time. }{End SubSec Examining Environmental Objects}