Dependencies


A dependency is a global variable (the dependent variable) and an associated definition that is like a function with no arguments. Values can be explicitly set and referenced in exactly the same ways as for a global variable, but they can also be set through the associated definition.

In this chapter the basic characteristics of dependencies are developed through a series of examples, and are then collected in a definition of dependencies. Then, itemwise dependencies are defined and exemplified. Finally, cyclic dependencies are discussed.

Creation and Deletion

A dependency is created by specifying its definition, in the form name:expression. The variable name can be either new or pre-existing. The body of the definition (which follows the colon) is entered in the same way that the body of any defined function is entered, with the same syntax rules, and the variable name can appear within it. The description of Execute in Context shows how you can define dependencies of the same form in several contexts at once. Because a dependency definition contains a colon, it cannot be a statement in a defined function or another dependency definition. To create a dependency within such a definition, use Execute in Context or the like.

A dependency, both variable and definition, can be deleted, and its storage freed, by the Expunge function (_ex) or command ($ex). The Remove Dependency Definition function, (_undef) and command ($undef), as their names imply, delete only the definition, leaving an ordinary variable with all its other properties intact, e.g., its value and any callback function set on it (see "Callback Functions"). They do not cause an evaluation before they remove the definition.

Evaluation

The first time a dependency is referenced after it is created, (assuming it has not been explicitly specified in the meantime) its definition is evaluated and the result is both saved and returned. When the dependency is referenced again, the saved value is returned if it has not been marked invalid in the meantime; otherwise the definition is evaluated and the new result is saved and returned. The saved value can also be set through ordinary Assignment, and this value will be returned until it is marked invalid. For example:
     a3        Define a global variable a
     b:a*2      Define a dependency b
     b
 9              The definition of b is evaluated.
     b
 9              The saved value of b is returned.
     a4        The saved value of b is marked invalid.
     b
 16             The definition of b is evaluated.
     b13       Specify a value for b
     b
 13             The saved value of b is returned.
     a5        The saved value of b is marked invalid.
     b
 25             The definition of b is evaluated.
The rules of localization within the definition of a dependency are the same as those for ordinary functions.

When a dependency is defined, the value, if any, of the dependent variable is marked invalid - not erased, just marked invalid. After that, the saved value (which continues to be retained) is marked invalid (evaluation needed) whenever the source of a value or the dependency itself is changed - i.e.,

A visible use of a name occurs when the name appears in A+ code directly, and not in a character string or a symbol. A use that is not visible is an implicit reference, which occurs through the use of Execute () or Value (%).

For example, if

     a100
     b:a*2
     f x:3+x
     df:a+b+f 2000
     df
 12103             The definition of df is evaluated.
then any one of the following will cause the value of df (saved by the last input line) to be marked invalid:
     a50
     b:a*3
     b625
     f x:4x
     df:a+b+f 3000
Only changes to global variables, functions, and dependencies that have a visible use will cause the saved value of a dependency to be marked invalid: implicit references will not. Moreover, changes to global variables that are assigned but not referenced in a dependency definition will not cause its saved value to be marked invalid. For example, in the dependency df defined below, changing x will cause the saved value of df to be marked invalid, but changing y or z will not, because there are no visible uses of them, and c is only set, not referenced (it is the right argument of the specification, ('y')+%`z,  that is referenced). Entering a new definition of df will of course mark any saved value invalid.
     df:x+.c('y')+%`z   The saved value of df is marked invalid.
     x10
     y100
     z1000
     df
 1110
     x20       The saved value of df is marked invalid.
     df
 1120
     y200      df not marked: no visible use in the definition.
     df
 1120
     z2000     df not marked: no visible use in the definition.
     df
 1120
     c50   df not marked: in the definition c is only assigned, not referenced.
     df
 1120
When a dependency definition is executed, the dependent variable is first marked valid. This validation allows the variable to be referenced during evaluation, either within the definition or in asynchronous execution such as a callback, and it provides a fallback value, which may possibly be of some use.

If execution of a dependency fails, a suspension occurs, as here, where n has no value:

     m:3n
     m
 .n: value
When the suspension is cleared, one of two things happens; if the dependency does not have a saved value, as in this example, then a value error on its name occurs:
*     
 .m: value
*
If, however, it has a saved value then that value has been marked valid. It is returned when the suspension is cleared. Continuing the example:
*          Clear the previous suspension in this example.
     m5    Now m has a saved value that is not marked invalid.
     n'a'  Marks m's saved value invalid and makes its definition erroneous.
     m
 : type    The saved value has now been marked valid.
*     m
* 5
*          Clear the suspension.
 5          The saved value is returned.
More examples of dependencies are presented in the chapters that follow.

Dependencies Defined

A dependency is a global variable, the dependent variable, with an associated niladic definition. It is established by
     name:definition
or
     name[indexname]:definition
The latter form is an itemwise dependency and is treated separately below.

The rules of localization for a dependency definition are the same as those for ordinary functions, and indeed it is interpreted exactly as the body of a niladic function would be. name may appear within definition.

A dependency has a saved value once its current definition has been evaluated or a value has been explicitly assigned. If the variable had a value before the dependency was established, the dependency has a saved value immediately after establishment, but that value is marked invalid.

   Evaluation of Dependencies

When referenced, the value of a dependency is determined as follows:

A dependency that is displayed by s in any sector or workspace and is not iconized is referenced each time A+ goes through its main loop, and likewise if it is bound to the `reference class.

Note that if a dependency has a stored value that is marked invalid, a reference triggers an evaluation not only when the reference requires the value, as in `dep is `table,  but also when the reference does not require the value, as in `class of `dep (supposing `dep not shown, so that its invalidation did not trigger an immediate evaluation).

   Invalidation of Dependencies

The saved value of a dependency is marked invalid:

A
global object is visibly referenced if its name occurs as the source of a value (not just as a specification target) outside an argument to Execute or Value. A change to such an object occurs when:

Note that a global variable that is set in a dependency but never referenced is not a visibly referenced object.

The value of a dependency is marked valid just before its definition is evaluated (if there is a saved value) and whenever a value is explicitly Assigned to the dependent variable - including, of course, at the end of a successful execution of its definition.

A change to a visibly referenced global variable made during evaluation does not cause the dependent variable's value to be marked invalid. Consider

     m:{mm+n; (n)10n; m+n}
     m100
     n1
     m
 111
n1 marks the saved value of m invalid, so the next statement triggers evaluation of its definition. The invalidation mark is changed to an under-evaluation mark, and mm+n uses the previous saved value to produce a new saved value (101). Respecifying n (as 10) at this point does not invalidate this new saved value, since it is marked as under evaluation, and it is used in the final m+n.

On the other hand, a new definition unconditionally invalidates a saved value, as in

     m:{mm+n; "m:n"; m+n}
     m100
     n1
     m
 2
where in the final m+n the new definition (m:n) is executed to evaluate m.

Code in callbacks is treated in this respect like code shown and called explicitly in the body of the dependency. When a dependency that is being evaluated triggers a callback function on another variable, any change made to a variable that is visibly referenced in the dependency does not mark the saved value of the dependency invalid. E.g.,

     {a0; b3;}
     a:(c)10b     Marks the saved value of a invalid.
     f{}:(b)10a   Set b in a callback function.
     `c _scb (f;)   Trigger a callback from c in the dependency definition.
     a              Dependency is evaluated.
 0 10 20
     b              See that b was set in the callback.
 0                  Yes, from a's original stored value.
     a
 0 10 20            The value from the evaluation.
Note that an evaluation is not completed until all callbacks triggered by it have been finished.

If a variable is set during its own callback, its dependents are invalidated (again).

Itemwise Dependencies

Itemwise dependencies are intended to reduce redundant computation by allowing the items of a dependent variable to be marked invalid separately. Only the first axis is singled out in this way. The form is the same as for ordinary dependencies except that Bracket Indexing and an index name are used in the definition:
     name[indexname]:body_of_definition
The variable named by the index name is local and can be used for any purpose within the definition. Since this variable is local, the index name must be unqualified.

A simple example of an itemwise dependency is

     y[i]:f{m[i];c}
Whenever c is changed - even just one of its items -, all of the saved value of y is marked invalid, but when items of m are appropriately changed, then only the corresponding items of the saved value of y are marked invalid.

   Recognition of Itemwise Changes

For itemwise invalidation of the dependent variable, any change in a variable on which the dependency depends itemwise must be made by Bracket Indexing, Choose, or Append Assignment. In the example just shown (leaving aside for the moment an incompatibility between Append and the others), either
   m[3756]expression
or
   (3756#m)expression
marks just y[3756] invalid, and, more generally, each of
   ((expr1)#m)expr2
and
   m[expr3]expr4
and
   m[,]expr5
marks items of y invalid.

All of y, however, is marked invalid by

   ((3756=#m)/m)expression
and, of course,
   m[;5]expression
Moreover, either pair of statements
   m[3756]expression
   m[,]expr5
or
   m[,]expr5
   m[3756]expression
mark all of y invalid (assuming no updating of y between the statements). In effect, when y is updated, it is updated by just one of a Bracket Assignment, an Append Assignment, or an ordinary Assignment. Therefore, itemwise invalidations caused by a Bracket Assignment and an Append Assignment cannot be pending at the same time. (Itemwise invalidations from several Bracket Assignments or several Append Assignments can be pending simultaneously, of course.)

If a is a large itemwise dependency and you know at some point in the code that you may be about to cause a total invalidation in one of the two manners described just above, you might consider avoiding the total invalidation by forcing an itemwise evaluation with a trivial statement such as
{a;};

In y[i]:m[i]  all  of y is marked invalid by m[;1]expr . As discussed above, Append Assignment is recognized as an itemwise change, except that a total invalidation occurs when otherwise an itemwise change from an Append Assignment would be pending at the same time as an itemwise change from a Bracket Indexing or Choose.

   Recognition of Itemwise Dependence

In an itemwise dependency definition, only the form shown, Bracket Indexing with just an index name and no semicolons, is allowed on the left. If any other form appears, the dependency is total, no matter what the rest of the definition is like.

In the body of the definition, to the right of the colon, a similar, but slightly less stringent, rule holds for each variable. To have the dependency depend itemwise on a variable, every referencing of that variable must be a Bracket Indexing using just that same index name for the first axis. None of the following definitions will allow updating of less than all of ns:

ns[i]:(sr+nw)[i]         An expression, not a variable, is being indexed.
ns[i]:{ji;sr[j]+nw[j]}  The index has same value, but not same name.
ns[i]:(i#sr)+i#nw        Choose, not Bracket Indexing, used in the def.
ns[i]:sr[i+1]+nw[i+1]    Indices consist of more than just index name.
ns[i]:sr[i]-sr[i-1]      The bad occurrence negates the good one.
On the other hand, indexing along other axes does not interfere with itemwise invalidation:
     ns[i]:sr[i;50],@1 nw[i;50+50]
allows updating of only the affected items of ns. Obviously, any dependence on a function or operator is total.

Note: At the present time, the parser may confuse the dependent variable with a function when parsing the body of an itemwise dependency definition. If such a parsing error occurs, replace the dependent variable, b, say, in the body by its fully qualified form, cxt.b, say, if the context is known and otherwise by %`b (or more likely (%`b)) to give the parser the hint it needs.

   The Time and Form of Evaluation of an Itemwise Dependency

When an itemwise dependency is defined or its definition is changed, its saved value, if any, is marked invalid, just as is done for any dependency. If the next event for it is a reference, its definition is evaluated, with the index name being given the value Null, to indicate all items (see next section). If at some later time any change occurs in a function, operator, or variable on which it depends, and that change is not recognized as itemwise, then the saved value is again marked invalid. If still later the definition is evaluated, the value of the index name is again the vector Null (again, see next section). Just as for an ordinary dependency, an evaluation with Null index for a variable bound to a screen display class can lead, sometimes with no warning or error message, to a retention of the saved value (no longer marked invalid).

Now suppose a dependent variable is referenced after a series of changes that are recognized as itemwise to variables on which it depends itemwise. (Indices modified by a callback may not be recognized.) Suppose further that no total invalidation has occurred. Then the dependency definition is evaluated with an index vector consisting of the indices for which the changes were recognized, listed in the order in which the changes took place, but without duplication. This vector may contain all indices of the dependent variable; it will nevertheless not be transformed into the Null. Just as for a total invalidation, an evaluation for a variable bound to a screen display class can lead, sometimes with no warning or error message, to a retention of the saved value (no longer marked invalid). Furthermore, for any dependent variable, evaluation with a value that is impermissible for Bracket Indexing leads to validation of the saved value; e.g., if int[i]:fl[i] and fl is set to a floating-point vector, int is set to an integer vector, fl[2] is set to a value that cannot be coerced to an integer, and then int is referenced, the evaluation fails and the saved value of int (no longer marked invalid) is returned, without any error or warning message.

If a Selective Assignment is explicitly made to a dependent variable and there are pending itemwise assignments, the definition is evaluated, for all the pending indices, before the Selective Assignment is made.

When any reference is made to an itemwise dependency, including one by Bracket Indexing, if the saved value is marked invalid, even just itemwise, its definition is evaluated before the reference is executed. Indices are not compared to see whether only valid items are being referenced, so that an evaluation is not actually needed.

If a dependency is defined and the dependent variable is explicitly assigned a value before any reference, then a reference will not trigger an evaluation until one of the variables upon which the dependency depends is changed. In particular, itemwise dependency on a particular variable may only gradually be reflected in the value of the dependency, as illustrated in this example:

     b10+10
     a[i]:{i;'---';b[i]}   Entire saved value of a marked invalid.
     a10                   Now it is not marked invalid.
     a
 0 1 2 3 4 5 6 7 8 9         Uses saved value.
     b[3 5]103 105          Pending change for a[3 5]
     a[0]                    Trigger an evaluation.
 3 5                         Just the changes since a10
---
 0
     a
 0 1 2 103 4 105 6 7 8 9     Old a, with a little new b
     b
 10 11 12 103 14 105 16 17 18 19
You usually want all the dependency to be honored from the beginning, and usually there is no problem, because you do not give an explicit value to a dependency that you have just defined. You should be aware, however, of the use of the saved value in the circumstances just illustrated.

   Evaluation When the Index is the Null

For any vector of indices i that is not the Null, the itemwise dependency definition a[i]:... can be thought of as being executed in the form a[i]... . When the index vector is the Null, however, this view would be incorrect. It is, rather, executed in the form a... . The shape of a can be changed by a total invalidation.

To repeat, for emphasis: when there is a recognized total evaluation of an itemwise dependency:

Cyclic Dependencies

So far only acyclic dependencies have been discussed, i.e., sets of dependencies with no recursive references, where no dependency depends on itself. The Debugging State system command ($dbg) provides a useful tool for analyzing recursive, or cyclic dependencies.

Example 1. Evaluating Cyclic Dependencies

The first example illustrates an important property of cyclic dependencies: if in the course of evaluating a dependency, its name is recursively referenced, that reference will be satisfied with the previously saved value (if there is no saved value, a value error occurs). Unlike a recursive function, a cyclic dependency cannot cause an infinite recursion. For example:

     $dbg dep 1     Show dependency evaluations.
     a:b+2          Marks the saved value of a invalid.
     b:a+g+2
     a12           Assign a and b values to avoid value errors.
     b5            They are now not marked invalid.
     g10           Saved value of b is now marked invalid, so a is also.
     a
    Dependency .a evaluation entered
      Dependency .b evaluation entered
      Dependency .b evaluation exited
    Dependency .a evaluation exited
 26
The new value of a is 26, which means the new value of b is 24. Since g is 10, a must have been 12 when b was evaluated, which was its saved value at the time.

Example 2. Interrelated quantities (rate, yield, spread)

Cyclic dependencies arise quite naturally in applications as sets of interrelated variables. For example, consider the following mutual relationships among underlying rate u, yield y, and spread s:

     y:u+s
     u:y-s
     s:y-u
For initialization set any two of these quantities. After that, if any one of the quantities is set the other two will be automatically updated when referenced. For example:
     $dbg dep 1
     (u;s)(0.08;0.005)  Set both at once else one invalidates the other.
     y            Defining dependency marked any saved value of y invalid.
    Dependency .y evaluation entered
    Dependency .y evaluation exited
 0.085
     y0.09       Marks the saved values of both u and s invalid.
     u
    Dependency .u evaluation entered
      Dependency .s evaluation entered
      Dependency .s evaluation exited
    Dependency .u evaluation exited
 0.08
     s
 0.01             The saved value is returned.
When y was reassigned, the saved values of u and s were marked invalid. When u was then referenced its definition was evaluated, causing s and y to be referenced. The reference to s caused it to be evaluated, using the new value for y and the old value for u. This new value for s was then used with the new value of y to produce a new value of u. Because of the selfconsistency of the mathematical expressions, the new value of u is the same as the old one. However, these computed values would not be the same if u and s had been referenced in the opposite order. To verify this, reproduce the example up to the references of u and s, and then reference them in the opposite order:
     (u;s)(0.08;0.005)
     y
    Dependency .y evaluation entered
    Dependency .y evaluation exited
 0.085

     y0.09
     s
    Dependency .s evaluation entered
      Dependency .u evaluation entered
      Dependency .u evaluation exited
    Dependency .s evaluation exited
 0.005

     u
 0.085
Reasoning as before, u now has a different value from the one originally specified, but s does not. Of course it is not always possible to know the order in which dependencies will reevaluated, which means that one cannot be certain of the new values in cases like this. The only consistent way to use n cyclic dependencies like these is to explicitly set any n-1 of the quantities and use the dependent definition only for the remaining one.

How can the values of several cyclic dependencies be set independently without causing one another's saved values to be marked invalid? In the above example, if y is first specified and then u is specified, the specification of u will cause the saved value of y to be marked invalid, and therefore when y is referenced it will not necessarily return the value to which it was set. The answer to this problem is to set them in a strand assignment, because at the end it marks as valid the values that it has just saved for all targets that are dependencies. Continuing the above example:

     (y;s)(0.09;0.005)
     y
 0.09
     s
 0.005
     u
    Dependency .u evaluation entered
    Dependency .u evaluation exited
 0.85
In practice, the values for y and s may not be conveniently available at the same time, so the information on the right of the strand assignment should be maintained in a set of auxiliary global variables. For this example these variables will be denoted by yA, uA, and sA.
     yA0.09           At some point in an application.
     sA0.005          Probably at some other point.
     (y;s)(yA;sA)     Later, and before referencing u
     u
    Dependency .u evaluation entered
    Dependency .u evaluation exited
 0.085
     uAu              Keep the auxiliary of u current.
By keeping the auxiliary variables current, it is not necessary for an application to respecify all n-1 dependencies in a cyclic set before evaluating the n-th: those not respecified will simply use their current saved values. Continuing the above example:
     yA0.095          A subsequent respecifying of yA
     (y;u)(yA;uA)     Later, before referencing s. Old value of uA used.
     s
    Dependency .s evaluation entered
    Dependency .s evaluation exited
 0.01
     sAs              Keep the auxiliary of s current.
The easiest way to keep the auxiliary variables current with the dependency values is to make them into dependencies as well:
     yA:y
     sA:s
     uA:u
The strand assignments can be incorporated in a function to be called when the current set of assignments to the auxiliary variables has been completed, and before the uncommitted dependency is referenced:
     commit x:(%0x)%1x
For example, the above strand assignment is equivalent to commit(`y`u;`yA`uA).

The use of auxiliary dependencies also makes it easy to cancel, or back out, changes simply by resetting the values of these dependencies to those when the last commitment was made. In the above example, if:

     cancel x:(%1x)%0x
then the values of the auxiliary variables can be reset to their values at the time of the last commitment by:
     cancel(`y`u`s;`yA`uA`sA)

System Functions and System Commands for Dependencies

The system functions and commands discussed here are described in "System Functions", and "System Commands".

The following example will be used to illustrate the system functions and system commands that apply to dependencies.

     p2 31.23 4.5 20 5.6 7 8.95
     p
 1.23  4.5  20
 5.6   7     8.95
     n2 310 1 2 5 3  1
     n
 10  1  2
  5  3  1
     fn{x}:x
In the definitions that follow, m is dependent on p, n, and fn; ct is dependent on m; and gt is dependent on ct.
     m:pfn{n}          Price times number.
     ct:+/m             Column totals.
     gt:+/ct            Grand total.
The system command $deps lists the names of the dependencies, while the system function _nl provides such a list as a vector of symbols when given `deps as an argument:
     $deps
 m ct gt
     list_nl{;`deps}
     list
 `m `ct `gt
     $vars
 p n
     _nl{;`vars}
 `p `n
Dependencies are global variables, and when they have saved values, their names appear in variable lists.
     ct
 40.3 25.5 48.95
     $vars
 p n m ct   The evaluation of ct caused evaluation of m, but not of gt.
The
system command $def dep displays the definition of the dependency dep, while the system function _def `dep returns that definition as a character vector:
     $def gt
gt:+/ct
     def_def `gt
     def
gt:+/ct
The
system command $dep name lists all dependencies in which name is explicitly referenced, where name can be any name, but the only meaningful names are those of global variables, dependencies, or functions. The corresponding system function is _dep:
     $dep n
m
     $dep fn
m
     _dep `ct
 `.gt
The
system function _alldep is the transitive closure of _dep with duplicates removed. The value of _alldep `name is a list consisting of the unique names in _dep `name, _dep_dep `name,  etc.
     _alldep `m
 `.ct `.gt
The command $undef v and function _undef v remove the dependency definition for v while leaving all the other properties of v intact. Forcing evaluation of v just before such a removal will save its latest value.

Finally, there is the Debugging State system command $dbg. The complete definition is given above, in the chapter on system commands. The use illustrated here is $dbg dep 1 for tracing dependency evaluation. Whenever a dependency is referenced, an "entered" and an "exited" message is displayed for each dependency whose definition had to be evaluated in order to satisfy the reference. In the above example, ct has already been referenced. If it is referenced again, its saved value is returned, so no dependencies are evaluated. If gt is referenced, however, its definition will be evaluated.

     $dbg dep 1
     ct
 40.3 25.5 48.95
     gt
    Dependency .gt evaluation entered
    Dependency .gt evaluation exited
 114.75
If one of the underlying variables is changed, all the dependencies are marked for evaluation, so all their names appear when gt is subsequently referenced:
     n[1;1]4
     gt
    Dependency .gt evaluation entered
      Dependency .ct evaluation entered
        Dependency .m evaluation entered
        Dependency .m evaluation exited
      Dependency .ct evaluation exited
    Dependency .gt evaluation exited
 121.75
Note that had m, ct, and gt been defined as niladic functions, the evaluated results illustrated in this example would have been the same. However, niladic functions have no saved values and are therefore always evaluated when referenced. Consequently dependencies provide a generally more efficient evaluation scheme.

doc@aplusdev.org© Copyright 1995–2008 Morgan Stanley Dean Witter & Co. All rights reserved.