Local Variables and the State Indicator
Vector 8:2, page 27
Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 27
One of the most important things we learn when we start working in APL is to keep track of variables. A pitfall we all discover at some stage or other is the loss of perhaps an hour’s work because of a moment’s inattention when we overwrite some important set of data. Perhaps we have laboriously copied a few hundred values into a vector called J, and then we execute a function containing a statement like J←1.
Well, as they say, "it can happen to anyone" - and of course if we have any sense at all we will have saved the workspace as soon as we entered the data. We always do, don’t we? (except when the phone rings, or someone knocks at our door…)
One way of reducing the risk in this area is to use local variables whenever possible. In fact, we meet local variables before we realise what they are: as soon as you use a function with an argument, you are creating local variables. For example, a simple function like MEAN:
The variables RES and X are local. To demonstrate this:
Note that, in order to calculate the mean of 1 2 3, inside the function MEAN the value 1 2 3 must have been given to X, and the result 2 must have been assigned to RES. In spite of this, the original values have not been overwritten.
Suppose we rewrite our function MEAN, calculating the number of values, N, before doing the summation:
We should still have the original values of X and RES, but we need to test the effect on N:
The value 77 has been overwritten by the value 3, which was assigned to N inside the function when the statement MEAN 1 3 5 was executed. This can be avoided by explicitly making N a local variable. This is done by adding it to line [0] (the header) of the function, separated from it by a semicolon. So edit the function:
As you can see, the data in N has been protected by the declaration of N as a local variable inside the function. Now let’s try something a bit more difficult.
OK - what values do you think are in X, Y and Z now? (answer and explanation at the end of this article.)
It’s all very well making sense of what comes out of a function when it is working the way you think it should - but what if something goes wrong? Can we find out where we are, and the current values of the variables? Yes, we can if we consult the State Indicator by typing )SI. First, let’s start with a clear workspace and enter a simple function:
Now, if your function has worked correctly first time, you can carry straight on. But if you had to have more than one shot at it, your State Indicator may not be clear. Type )SI. There should be no response. If there is a response, you need to clear the State Indicator. Unfortunately, different implementations of APL require different commands to do this. One of the following should do the trick:
Our aim is to get no response from )SI.
Next, we shall deliberately cause an error to occur:
At this point, we should get a report that a DOMAIN ERROR has occurred. We have tried to divide 6 by 0, and found it to be impossible. What does the State Indicator have to say?
The function DIVIDE has been suspended (* means suspended, i.e. interrupted because of an error). The error has occurred in line [1] (fairly obvious in a 1-line function, but informative in general). What about variable values?
We should be surprised that these variables exist at all - they are local to the function, and would have disappeared if the function had terminated correctly. But local variables remain active when functions are suspended. Furthermore, we can change their values, and then continue execution of the function:
The last statement means "resume execution at line 1". The result is 6 divided by 2. What has happened to SI?
No response - SI is clear. In this rather trivial example, we have caused an error to occur, corrected the fault in the data, and resumed execution to a successful conclusion. Let’s create a second function:
This function should sum the inverses of its two arguments:
(The inverses of 2 and 4 are 0.5 and 0.25, which sum to 0.75.)
Once again, a DOMAIN ERROR is produced.
This time there are two entries in SI. As before, DIVIDE is suspended (indicated by *). Below this we have the function SUMINVERT which is pendent (without a *). Pendent functions have called other functions which then failed to complete their execution - in this case, DIVIDE became suspended. What are the values of X and Y?
No, they do not have the values 2 and 0. Why? We must think about what is happening inside function DIVIDE - the function where the error actually occurred. It is the second call to DIVIDE that has gone wrong - the 1 DIVIDE Y bit. At this point, Y has the value 0, so DIVIDE is being passed the parameters (X,Y) = (1,0), and this is still the situation when it crashes. Let’s put it right:
Before we resume execution, just one other point:
(It doesn’t work: you can’t erase functions that are suspended or pendent.)
Clear again. Note: execution is resumed in line [1] of the top function in the SI list - the last one to get suspended.
Two final notes:
- a suspended function can be terminated (together with any consequently pendent functions) by
→(with no argument); - the State Indicator can always be cleared using
)RESET(or one of the other commands described earlier).
If you have never played with local variables and the State Indicator before, try making a few more functions crash - functions calling functions calling functions… etc. Familiarity with the concepts will encourage you to use them "for real" when the opportunity arises. The answer to the local variable problem:
Initially, X is 1 and Y is 2. Inside FUN1, X becomes 4 and Y becomes 7, then FUN2 is called. Inside FUN2, X becomes 2 and Y becomes 5. On return to FUN1, as Y is local to FUN2, it reverts to the value 7, but X stays as 2. Hence Z is the sum of 2 (X) and 7 (Y), i.e. 9. We now have the values (X,Y,Z) = (2,7,9). On return from FUN1, as X is local to FUN1, it reverts to the value 1 (its original global value). Hence the result (X,Y,Z) = (1,7,9).