{Begin SubSec Active Values} {Title Active Values} {Text {Tag ActiveValues} Active values provide a way of invoking procedures 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. This section presents the notation for creating active values. Then, the concept of nested active values is introduced. The nesting property enables many of the important applications of active values by supporting composition of the access functions. Next is described how to use active values as the default values in a class, and how to share them. Finally, the standard arguments to active value access functions are described, along with LOOPS functions that can be used in user-defined access functions. {Begin SubSec Active Values Notation} {Title Active Values Notation} {Text The notation for an active value illustrates its three parts: {lispcode #({arg localState} {arg getFn} {arg putFn})} This notation is converted by a read macro into an instance of the Interlisp data type {lisp activeValue}. The {arg localState} field is used as 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 whose value is an active value. 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. }{End SubSec Active Values Notation} {Begin SubSec Nested Active Values} {Title Nested Active Values} {Text Often it is desirable to associate multiple access functions with a variable. For example, we may want more than one process to monitor the state of some objects (e.g., a debugging process and a display process). To preserve the isolation of these processes, it is important that they be able to work independently. LOOPS uses nested active values as a way of composing these functions. Nested active values are arranged so that the innermost active value is stored in the {arg localState} of the penultimate {arg localState}, and the outermost active value is the immediate value of the variable. Put operations to a variable through such nested active values trigger the {arg putFn}s in sequence from the outermost to the innermost. For example, suppose the variable tracing facility were used to trace access of the {lisp position} variable from the model/view controller example ({PageRef Figure ModelViewExample}). The resulting active value would look like {lispcode #( #({arg Pos1} NIL UpdateDisplay) GettingTracedVar SettingTracedVar)} An attempt to set the position variable would cause the function {lisp SettingTracedVar} to be called with the new value as one of its arguments. {lisp SettingTracedVar} would operate and call the LOOPS function {fn PutLocalState} to set its own {arg localState}. This, in turn, would trigger the inner active value causing {lisp UpdateDisplay} to be invoked. Get operations work in the opposite order. If there are three nested active values, a request to get the value will cause the innermost {arg getFn} (if any) to run, followed by the middle {arg getFn} (if any), followed by the outermost {arg getFn} (if any) whose value is returned by the get operation. Each {arg getFn} sees only the value returned by the next nested {arg getFn}, and the innermost {arg getFn} sees the value stored in its localState. LOOPS provides functions for embedding and removing active values from variables. This idea of functional composition for nested active values is most appropriate when the order of composition does not matter. We have resisted the development of other combinators for the functions using the same parsimony arguments that we used earlier about specializing and combining methods. Just as inheritance from multiple super classes works most simply when the super classes describe independent features, active values work most simply when they interface between independent processes using simple functional composition. Any more sophisticated control is seen as overloading the active value mechanism. The escape for more complex cases is to combine the implicit access functions using Interlisp control structures to express the interactions. }{End SubSec Nested Active Values} {Begin SubSec Active Values as Default Values} {Title Active Values as Default Values} {Text Suppose that {lisp I} is an instance of a class with an instance variable {lisp V}, whose default value is the active value {lisp A}. Further suppose that the value of {lisp V} in the instance {lisp I} has never been set. The first time {lisp (PutValue I V {arg exp})} is invoked, a copy of {lisp A} is made. This copy is inserted in the instance itself as the the value of the instance variable, with pointers to the same contents as {lisp A}. Then the {arg putFn} is invoked, with the copy as the {arg activeVal} argument; this copy of {lisp A} provides a place where local state can be stored private to {lisp I}. In some cases, one knows that the {arg putFn} will not actually write into the active value, and therefore the active value which is the default could be shared instead of needing to be copied. To indicate this, the {arg localState} of {lisp A} should be made the atom {lisp Shared}.{index Shared Atom} In the example below, the user knows that no change will be made in {lisp A} itself and thus uses a shared active value. Example: {lisp SUM} is a class with three instance variables, {lisp top}, {lisp bottom}, and {lisp sum}; {lisp top} and {lisp bottom} start with default values of {lisp 0}, and {lisp sum} is to be computed when asked for. One cannot update {lisp sum} independently. {lispcode [DEFCLASS SUM (MetaClass Class) (Supers Object) (InstanceVariables (top 0) (bottom 0) (sum #(Shared ComputeSum NoUpdatePermitted)) (ClassVariables) (Methods (printOn PrintColumn)]} The method for {lisp printOn} used in this example, and the {arg getFn}, {lisp ComputeSum}, and the {arg putFn}, {lisp NoUpdatePermitted}, are Lisp functions whose definitions are not shown here. {fn NoUpdatePermitted}{index NoUpdatePermitted Fn} is available as part of the kernel. }{End SubSec Active Values as Default Values} {Begin SubSec Standard Access Functions} {Title Standard Access Functions} {Text LOOPS provides a convenient set of functions for some common applications. For example, {fn NoUpdatePermitted}, described in the example above, is used to stop update of the {arg localState} of an active value. {fn FirstFetch}{index FirstFetch Fn} is a standard {arg getFn} that expects the {arg localState} of its active value to be an Interlisp expression to be evaluated; on the first fetch, the instance variable is set to the result of evaluating the expression. This is illustrated in {FigureRef TestDatum}, which shows a class {lisp TestDatum} that describes an instance variable {lisp sampleX}, to be computed on the first time that it is fetched, and then cached for future references. At the time of activation of {fn FirstFetch}, {lisp self} and {lisp varName} are bound to the instance and instance variable name in which the active value was found. {Begin Figure} {Tag TestDatum} {Text {lispcode (DEFCLASS TestDatum (MetaClass Class) (... (InstanceVariables (sampleX #((RAND 0. 100.) FirstFetch)))...)} } {Caption Using an active value to compute and cache a value for a variable on the first fetch. } {End Figure} In some applications it is important to be able to access values indirectly from other instances. For example, Steele [Steele80] has recommended this as approach for implementing equality constraints. {FigureRef JoeAsFatherPerspective} shows a way of achieving this by using using the standard access functions {fn GetIndirect} and {fn PutIndirect}. {Begin Figure} {Tag JoeAsFatherPerspective} {Text {lispcode (DEFINST JoeAsFatherPerspective ... (InstanceVariables (age #((#$JoeAsManPerspective age) GetIndirect PutIndirect)) ...} } {Caption Active values can be used to provide indirect access to values. This is useful when it is desired for a variable in one instance to reflect the value of a variable stored elsewhere. In this example, the instance {lisp #$JoeAsFatherPerspective} has an {lisp age} variable which always has the same value as the {lisp age} variable of the instance {lisp JoeAsManPerspective}. } {End Figure} For some uses, the user may want to compute a default value if given, but replace the active value by the value given if the user sets the value of a variable. For this the user can employ the system provided {arg putFn} of {fn ReplaceMe},{index ReplaceMe Fn} as in: {lispcode #(NIL ComputeGoodValue ReplaceMe)} If this value is made the default in a class, then when a program tries to set this value, the instance will contain the value set. However, if the user tried to fetch the value form this variable before setting it, the {arg getFn} {lisp ComputeGoodValue} would be invoked. }{End SubSec Standard Access Functions} {Begin SubSec User-Defined Access Functions} {Title User-Defined Access Functions} {Text The {arg getFn} and {arg putFn} of an active value are functions that are called with standard arguments: {lisp ({arg self} {arg varName} {arg oldOrNewValue} {arg propName} {arg activeVal} {arg type})} These arguments are interpreted as follows: {Begin LabeledList arguments are interpreted as follows} {Label {arg self}} {Text The object containing this active value.} {Label {arg varName}} {Text The name of the variable where this active value was stored. This is {lisp NIL} if it is not stored in a variable.} {Label {arg oldOrNewValue}} {Text For a {arg getFn}, this is the {arg localState} of the active value. For a {arg putFn}, this is the new value to be stored in the active value.} {Label {arg propName}} {Text The name of a property. This is {lisp NIL} if the active value is not associated with the value of a property (i.e., if it is associated with the value of the variable itself).} {Label {arg activeVal}} {Text The active value in which this {arg getFn} or {arg putFn} was found.} {Label {arg type}} {Text This specifies where the active value is stored; {lisp NIL} means a instance variable, {lisp CV} means a class variable, {lisp CLASS} means a class property, or {lisp METHOD} means a method property.} {End LabeledList arguments are interpreted as follows} The value returned by the {arg getFn} is returned as the value of the get operation. The {arg putFn} is expected to make any necessary changes to the {arg localState}. This can be done using function {fn PutLocalState} described below. In changing the {arg localState}, embedded active values may be triggered. Given an active value, the following functions can be used to retrieve or store its {arg localState}: {FnDef {Name GetLocalState} {Args activeValue self varName propName type} } {FnDef {Name PutLocalState} {Args activeValue newValue self varName propName type} {Text {fn GetLocalState} returns the {arg localState} of the active value {arg activeValue}. {fn PutLocalState} stores {arg newValue} as the {arg localState} of the active value {arg activeValue}, and returns {arg newValue}. Note that it is necessary to pass these functions the values for {arg self}, {arg varName}, {arg propName}, and {arg type}, in case any imbedded active values are triggered. }} If the {arg localState} of the active value is itself an active value, then it will be triggered to obtain the {arg localState} argument for the {arg getFn}. For a {arg putFn}, an embedded active value will be triggered when the {arg putFn} calls {fn PutLocalState}. The following functions can be used to access the {arg localState} of an active value without triggering any embedded active values: {FnDef {Name GetLocalStateOnly} {Args activeValue} } {FnDef {Name PutLocalStateOnly} {Args activeValue newValue} {Text {fn GetLocalStateOnly} returns the value of the {arg localState} of the active value {arg activeValue}. {fn PutLocalStateOnly} stores {arg newValue} as the {arg localState} of the active value {arg activeValue}, and returns {arg newValue}. Both functions access the {arg localState} without triggering embedded active values. }} In some cases, it is important to be able to replace the entire active value expression by some quantity, independent of the depth of nesting of active values, without destroying the outer levels of nesting: {FnDef {Name ReplaceActiveValue} {Args activeVal newValue self varName propName type} {Text {fn ReplaceActiveValue} overwrites {arg activeVal} whereever it is (either directly as the value or property of an instance variable, or as the local state of an embedded active value) with {arg newValue} {fn ReplaceActiveValue} searches the value (property) determined by its arguments until it finds {arg activeVal} in the nesting. If {arg activeVal} is not found, an error is invoked. }} Example: Suppose that we have a class {lisp RandomDatum} which describes an instance variable {lisp sampleX}, which we want to be computed as a random number on the first time that it is fetched, and then returned as a constant on all future fetches. We could do this by defining the class as follows: {lispcode (DEFCLASS RandomDatum (MetaClass Class) (... (InstanceVariables (sampleX #(NIL SmashRandom ReplaceMe))) ...)} where the function {lisp SmashRandom} is defined as follows: {lispcode (LAMBDA (self varName value propName activeValue) (ReplaceActiveValue activeValue (RAND 0. 100.) self varName]} On the first fetch of the value of {lisp sampleX} in any instance of {lisp RandomDatum}, the function {lisp SmashRandom} over-writes the active value with a random number. This is a special case of the active value function {fn FirstFetch} described earlier. The function {fn MakeActiveValue} is used to make the value of some variable or property be an active value: {FnDef {Name MakeActiveValue} {Args self varOrSelector newGetFn newPutFn newLocalSt propName type} {Text {arg self} is the object, {arg varName} is typically the name of a variable when the active value is being placed in an instance variable. If the active value is being placed in a method, then {arg varName} should be bound to the selector name. Active values can also be used for class variables, or properties of instance or class variables, or methods. The interpretation of where to create the active value is determined by the argument {arg type}, which must be one of {lisp IV} (or {lisp NIL}), {lisp CV}, {lisp CLASS}, or {lisp METHOD}. If {arg newLocalSt} = {lisp EMBED}, then a new active value is always created, containing as its {arg localState} whatever was found by {fn GetItOnly} ({PageRef Fn GetItOnly}). For other values of {arg newLocalSt}, an active value is created only if the current value is not an active value; otherwise the old one is simply updated with {arg newLocalSt}, {arg newGetFn}, and {arg newPutFn}. If an old active value is being updated, then if {arg newGetFn} or {arg newPutFn} is {lisp NIL}, the old {arg getFn} or {arg putFn} is not overwritten. If {arg newGetFn} or {arg newPutFn} is {lisp T}, the old {arg getFn} or {arg putFn} is reset to {lisp NIL}. {note probably should use some flg like 'RESET instead of T --- mjsann} {note this function is overloaded --- mjsann} }} The easiest way to define a function for use in active values is to use the function {fn DefAVP}: {FnDef {Name DefAVP} {Args fnName putFlg} {Text {fn DefAVP} creates a template for defining an active value function and leaves the user in the Interlisp editor. {arg fnName} will be the name of the function and {arg putFlg} is {lisp T} if this is to be a {arg putFn} and {lisp NIL} if it is to be a {arg getFn}. }} For {arg getFn}s, the template is {lispcode [LAMBDA (self varName localSt propName activeVal type) (* This is a getFn for ...) localSt]} This template incorporates the standard arguments that a {arg getFn} receives, and the convention that they often return the value that is in their local state. For {arg putFn}s, the template is {lispcode [LAMBDA (self varName newValue propName activeVal type) (* This is a putFn for ...) (PutLocalState activeVal newValue self varName propName type)]} This template incorporates the standard arguments that a {arg putFn} receives, and the convention that they often put their resulting {arg newValue} in the {arg localState}. }{End SubSec User-Defined Access Functions} }{End SubSec Active Values}