Skip to content

Packaged Workspaces in APL2

Vector 7:2, page 50

implementations system interfaces development practice

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 50

Introduction

Packaged workspaces were introduced into APL2 in release 1.3, which became available about the beginning of 1988. This release was only installed at NatWest in the middle of 1989 - which explains why this paper was not written earlier.

For the benefit of those who have heard of packaged workspaces in APL2/PC, it should be pointed out that this is something completely different! Rather than being a run module, as are packaged workspaces in APL2/PC, packaged workspaces in APL2 closely resemble in functionality the subroutine libraries that are so useful in other languages.

In many ways, the most important property of the packaged workspace facility is that it provides a way of creating libraries of utilities that can be made available to all workspaces without the need to copy functions into the workspace at run time, and without having to worry about name conflicts for global variables or secondary functions. Packaged workspaces also offer a means of concealing from users all but the objects that they need to know about, and can produce savings in CPU time and in workspace requirements.

Certain details in what follows are specific to the MVS/TSO environment, though the general principles apply equally to VM/CMS.

Name Association

Access to objects in a packaged workspace is via the ⎕NA function which was introduced in APL2 release 1.2 to provide a means of using code written in languages other than APL via (Associated) Processor 11. When a workspace is packaged, variables, functions and operators may all be made accessible to other workspaces, as required. Once accessed, such objects, which may be referred to as external objects, can be used in the normal way.

There are three external functions supplied for use with packaged workspaces, viz. PACKAGE, EXP and QNS. To get hold of these (or other supplied external functions) one needs first to have allocated the distributed NAMES file APL2.NAMES.AP2TN011 to the ddname AP2TN011. Then, to access, say, PACKAGE requires the command

3 11 ⎕NA 'PACKAGE'

which should return the value 1.

In the right argument of ⎕NA, it is possible to supply an alias, by which the object will be known in the associating workspace, to avoid a name conflict. For example, there is a supplied routine OPTION which can be used to query or set invocation options; if it is desired to use this function in a workspace that already has an object with this name, then it can be associated as, say, IOPT by using the command

3 11 ⎕NA 'IOPT OPTION'

To find out what the name association of an object is, use ⎕NA monadically:

⎕NA 'IOPT'
3 11

The result is the left argument used with ⎕NA when activating the object, if it is an external object. For objects which are not external, the first element of the result is the name class of the object and the second element is 0.

When a workspace is saved, sufficient information relating to any name associations is stored to enable re-accessing of the respective objects when they are first referenced after the workspace is reloaded.

Packaging Workspaces

Suppose that one wishes to package a workspace called MONTHLY which is an end-user application with only two functions that need to be accessed from the command line: START and RESTART. The workspace must be saved in a SAM library - the technique does not work for workspaces stored on VSAM files. One must first allocate a file, TEST.OBJ, say, to ddname SYSPUNCH with a command like:

)HOST ALLOC DD(SYSPUNCH) DS(TEST.OBJ) NEW SP(5 5) TRA LRECL(80)
BLKSIZE(3200) RECFM(F B)

If the external function PACKAGE has been associated as described above, then the command

'START' 'RESTART' PACKAGE 'MONTHLY'

will cause an object deck to be placed in the file TEST.OBJ; this filename is returned as the result of PACKAGE. Finally, the object deck must be link-edited into a load library using a command of the form

)HOST LINK TEST.OBJ LOAD(LOADLIB(MONTHLY)) RMODE(ANY)

which will create a member MONTHLY in the partitioned dataset LOADLIB. (The parameter RMODE(ANY) should only be used under MVS/XA or ESA.)

In the present example, the list of items to be accessible, which forms the left argument to PACKAGE, has only two items, which is quite typical when packaging an entire application that is menu driven. In other cases, for example when one is packaging a utility workspace, the list of items to be accessible may be quite long. In any case, it is good practice to have this list stored as a variable, NAME_LIST, say, in the workspace. (If all objects are to be accessible then the left argument to PACKAGE may be omitted.)

For best results, before packaging a workspace, do a )COPY of the saved workspace into a clear workspace and )SAVE again. This causes the workspace to be compacted, clears the state indicator, and removes any traces and stops.

Accessing Objects in Packaged Workspaces

Objects in packaged workspaces are accessed by using ⎕NA, which takes as right argument the list of objects to be accessed. The left argument of ⎕NA has a number of possible forms, allowing a variety of ways of specifying how and where the system should search for the specified objects.

Continuing with the example above, one convenient way to access the objects in the packaged workspace requires that one first allocate the load library LOADLIB, with ddname, say, AP2LOAD. Then to access START one may use the command

'AP2LOAD.MONTHLY' 11 ⎕NA 'START'

MONTHLY being the name of our object module in LOADLIB. Both functions may be accessed in one command by using

(⊂'AP2LOAD.MONTHLY' 11) ⎕NA¨ 'START' 'RESTART'

The value 1 will be returned by ⎕NA for each object found and associated, 0 for those not associated. Assuming that the load library has been properly allocated and that the correct object module has been specified, the most likely reason for failure of the association request is that the specified name is already in use in the current workspace for an object that is not associated, or was associated using a different left argument to ⎕NA.

Another form for the left argument of ⎕NA is that described in section 2, i.e. the left argument is: 3 11. In this case, one indicates where the code is to be found using a NAMES file; entries are as used when employing ⎕NA to access code written in another language, except that one must add the tag link:APL. See the manual APL2 Programming: System Services Reference for further details.

The use of a NAMES file is perhaps somewhat more robust than locating code by allocating the load modules to a particular ddname, but it is often less convenient and is certainly more restrictive, as object names are required to be alphanumeric, and, in MVS, names are limited to eight characters; also, object names must be unique.

Name Scopes

From what has already been said about the avoidance of name conflicts, it will be apparent that the active workspace and each packaged workspace knows about only those objects which are local to itself, i.e. are within the name scope of the particular workspace; for an external object, i.e. one that has been associated via ⎕NA, this knowledge is restricted to pointers to the object’s "home" name scope.

The active workspace is referred to as the primary name scope, because, of course, this is where any APL session will start. As soon as an object declared via ⎕NA is encountered during execution, the system changes to the name scope from which that object comes and takes from there the value or definition of both the object and any objects that it may reference. If the object is a variable then its value is returned (or, if it is a shared variable, the corresponding auxiliary processor is accessed) and the calling name scope becomes current again. If the object is a function or operator then this secondary name scope will remain active either until execution of the function or operator terminates or until an object in another name scope is encountered. If execution suspends in a secondary name scope, then that name scope remains current and all and only those objects in that name scope are directly available to the user. To revert to the primary name scope use )RESET.

A change of name scope that occurs as a direct result of a call to an external function or operator (i.e. one declared using ⎕NA) is referred to as an explicit change of name scope. This contrasts with the implicit change of name scope that occurs during the execution of an externally defined operator called with a defined function operand: the call to the operator causes an explicit change of name scope, but if the operator references its operand then there will be an implicit change to the name scope from which the operator was called, for that is where the function is defined.

For example, suppose we have an external optimizing operator MINIMAX, which takes as left argument the function to be optimized and as right argument various parameters. A call to MINIMAX will be of the form

R ← FUN1 MINIMAX PARMS

Initially, execution will be in the name scope from which MINIMAX comes, but as soon as the function operand is encountered in the definition of MINIMAX, FUN1 will be invoked in its own name scope, causing an implicit change of name scope.

Accessing Objects in Other Active Name Scopes

As soon as one thinks of packaging utility functions or operators one discovers the need to access objects in other active name scopes. For example, an external workspace documentation function needs to work on the objects in the calling name scope, not in its own name scope; also, some utilities call others, e.g. for screen display and error handling. It is apparent that, unless one packages all utilities in a single workspace, it is quite possible for there to be objects from a number of name scopes on the stack, separating the primary name scope from the current name scope.

There are three ways of getting hold of objects in another name scope.

The obvious way is by using ⎕NA. An object X in the primary name scope can be made accessible to the current name scope (assumed different from the primary name scope) by using the command

'' 11 ⎕NA 'X'

where the null string indicates the primary name scope. This should return the value 1.

To assist in using ⎕NA like this, there is a supplied function QNS (Query Name Scope) which when executed in a name scope will return as its result the appropriate left argument for ⎕NA to associate with an object in that namescope. QNS is always used with a right argument of 0.

For example, suppose we have a packaged utility UTIL which will perform some function on objects not necessarily in the primary name scope, and that UTIL takes as its right argument a variable NAMESCOPE which identifies the calling name scope. Then UTIL can be called by

UTIL (QNS 0)

and within UTIL there might be the lines

NAMESCOPE ⎕NA 'NL ⎕NL'
VARS ← NL 2

The first line makes use of the alias facility of ⎕NA to get hold of the system function ⎕NL in the referenced name scope and the second will put into VARS a list of the variables there.

A second way to access objects in another active name scope is via the use of an externally defined operator, as described in the explanation of an implicit change of name scope in the previous section. The function operand (or operands) will execute in the name scope that called the operator. For example, if ⎕NL is used as such an operand, it will provide lists of objects in the calling name scope. It is apparent that this technique cannot readily be employed to get hold of objects in name scopes further away than the calling name scope.

The third technique is to use the supplied external function, EXP, which allows one to execute various forms of expression in the previous name scope. (Perhaps there should be a Vector competition to provide an explanation why EXP is a better acronym than EPNS.) EXP is called with a nested vector right argument and usually returns a result. The simplest form of usage is

RESULT ← EXP ⊂'string'

which will cause "string" to be executed in the previous name scope; any result (including the result of an assignment) will be returned as RESULT. For example, consider

EXP ⊂'VAR'
EXP ⊂'⎕CR ''FUN1'''
EXP ⊂'FUN2'
EXP ⊂'TEMP ← 0'
EXP ⊂'QNS 0'

The first of these will return the value of the variable VAR; the second will return the canonical representation of FUN1; the third will execute the niladic function FUN2 and return the result, if any; the fourth will assign the value 0 to TEMP and return this as result; the last example will return the identity of the previous name scope (assuming that QNS has been associated there).

To use EXP to execute a function in the previous name scope using arguments from the active name scope the syntax is

EXP 'FUN' RARG
or EXP LARG 'FUN' RARG

according as the function FUN is monadic or dyadic, where LARG and RARG are values in the current name scope. For example, the second of the previous examples can be replaced by

EXP '⎕CR' 'FUN1'

Finally, EXP can be used to assign a value to a variable in the previous name scope using the form

EXP 'VAR1' '←' VAR2

This will give to the variable VAR1 in the previous name scope the value of the variable VAR2 in the current name scope. Thus the fourth of the earlier examples can be replaced by

EXP 'TEMP' '←' 0

It is best to shun the first form of usage of EXP if one of the others can be employed, in order to avoid a proliferation of quote marks.

A call to EXP in the primary name scope will be executed there, so most functions that have been prepared for use from a packaged workspace will work properly when used with their definition in the active workspace.

Note that EXP causes an implicit change of name scope.

Error Trapping Example

In this section we shall consider what has to be done to package as utilities the functions used in a simple error trapping application. The purpose of the trapping procedure considered is not to provide any fancy corrective action; it simply displays on the screen an appropriate message when, say, a syntax error is encountered or the user presses attention, and then allows the program to restart at the main menu. Similar methods may be used when more sophisticated error trapping techniques are being employed.

The application workspaces in which the error trapping is used may be considered to have a latent expression which calls the function START. This will perform all initialization and then call RESTART, which brings up the main menu. Three other functions are also important in the present context:

PFK
which when activated (in immediate execution mode) by the pressing of PF key 4 causes RESTART to execute;
STACKSET
which creates an input stack for use when the keyboard opens for immediate execution after an interrupt has occurred;
RAWAPL
which retrieves and displays any error message.

In the original version of the workspace, the last two functions were as follows:

    ∇ STACKSET;A;STACK
[1]  ⍝ Prepare stack for attention exit
[2]   STACK←'(FIFO'
[3]   A←101 ⎕SVO 'STACK'
[4]   STACK←0 ⍝ Clear the stack
[5]   STACK←')SI'
[6]   STACK←'⍙ERR←⎕ET ⎕EM' ⍝ Save event details
[7]   STACK←')RESET'
[8]   STACK←'RAWAPL 1'
[9]   STACK←'PFK 4'
    ∇

An execution stack is set so that if an interrupt occurs then (starting with line 5): the state indicator is displayed so that there is a record in the session log; the event type and event message are saved in variable ⍙ERR; the state indicator is cleared; RAWAPL is called to display the event message; finally, the program is restarted by the call to PFK.

    ∇ RAWAPL X;M;T;⍙EM;⍙ET
[1]  ⍝ Entered if we get into raw APL by any mischance
[2]  ⍝ Global variable ⍙ERR holds event type and message
[3]   T←'An abnormal event has occurred'
[4]   (⍙ET ⍙EM)←⍙ERR
[5]   →('ATTENTION' 'INTERRUPT'≡¯2↑(' '≠⍙EM[1;])⊂⍙EM[1;])↓∆0
[6]   →∆1,⍴M←⊂'You hit attention.'
[7]  ∆0:M←⊂'Something strange has happened'
[8]   →(0∊⍴⍙EM)↓∆2
[9]   ⍙EM←⊃' '('(Error type is ',(⍕⍙ET),')')' '
[10] ∆2:M←M,⊂[2]' ',[1]⍙EM
[11]  M←M,(⊂''),⊂'Please report this to IT Systems Support'
[12] ∆1:M←M,(⊂''),⊂'Hit PF 3 to return to the primary panel'
[13]  A←T SCREEN∆DISPLAY ⊃M
    ∇

Function SCREEN∆DISPLAY is a utility which displays its matrix right argument under the title string in the left argument. At line 4 the event type and message are retrieved. The message to be displayed is then built up as a vector of vectors, with special cases if Attention was pressed or if the event message is empty.

The whole of the original application workspace can be packaged as a single unit without changes, as described earlier. We now consider what alterations have to be made to the functions just described if it is desired to package the utility functions from the application in a number of separate workspaces. At the very least we shall have separate packages for screen-handling, file-handling and miscellaneous utility functions. RAWAPL will go into the last of these and we shall consider that first.

The only reference in RAWAPL to the environment that called it is at line 4 where the variable ⍙ERR is referenced. If we replace this line by

[4] (⍙ET ⍙EM)←EXP ⊂'⍙ERR'

then all will be well: the value of ⍙ERR will be retrieved from the calling name scope.

Consider now what is put on the stack in STACKSET. The )RESET is performed before the call to RAWAPL in case the interrupt was caused by WORKSPACE FULL. Unfortunately, if the interrupt occurs in a packaged workspace, the variable ⍙ERR, in which we save the event type and message, will be lost when the )RESET returns us to the primary name scope. What is required is a function to repeatedly place the variable ERR in the previous name scope, until it finishes up in the primary name scope.

Such a function is the following:

    ∇ TNS MOLE PARMS;X
[1]  ⍝ Do an EXP on PARMS, and burrow further down if this is
[2]  ⍝ not the target name scope TNS
[3]  ⍝ Requires MOLE, EXP and QNS at all levels
[4]   X←EXP PARMS
[5]   →('' 11≡X←EXP 'QNS' 0)/0 ⍝ Quit if bottom or
[6]   →(TNS≡X)/0 ⍝ target level accessed
[7]   EXP TNS 'MOLE' PARMS
    ∇

We will need to make the following call (to be put on the stack at line 6.5 of STACKSET):

('' 11) MOLE '⍙ERR' '←' ⍙ERR

Then, when MOLE executes, line 4 will put a copy of ⍙ERR in the next earlier name scope, lines 5 and 6 check to see if we have finished, and line 7 repeats the call to MOLE, in the previous name scope. As indicated in the comment in line 3, MOLE has to exist already in each name scope that it accesses, as do EXP and QNS. It is possible to write the function to perform from each name scope a ⎕NA in the previous name scope to ensure that all required functions are there, but that seems an unnecessary complication. It certainly appears sensible to have EXP and QNS available in all workspaces that make use of packaged functions.

Having got this far, the next step is to package MOLE as, say, MOLE2:

    ∇ TNS MOLE2 PARMS;X
[1]  ⍝ Do an EXP on PARMS, and burrow further down if this is
[2]  ⍝ not the target name scope TNS - packaged version
[3]  ⍝ Requires MOLE2, EXP and QNS at all levels
[4]   X←EXP 'EXP' PARMS
[5]   →('' 11≡X←EXP 'EXP' ('QNS' 0))/0 ⍝ Quit if bottom or
[6]   →(TNS≡X)/0 ⍝ target level accessed
[7]   EXP 'EXP' (TNS 'MOLE2' PARMS)
    ∇

All that is required is another layer of EXPs, so that what was executed in the current name scope from MOLE is now executed in the previous name scope from MOLE2. The name of the function called in the extra line of STACKSET should be changed to MOLE2, and MOLE2 should be associated as an external function in all packaged and application workspaces.

Finally in this section, we consider the function PFK. The chief feature of PFK is that it causes RESTART to run when function key 4 is pressed; the effect of other function keys may vary from application to application. When testing with error trapping not activated, it is useful to be able to restart the application by pressing PF4, even when one is not in the primary name scope. This objective can be achieved by having, in each utility packaged workspace, a function PFK which resets the state indicator and then runs PFK in the primary name scope with the same parameter (key number) as that with which it was called. This is done by the following:

    ∇ PFK N;A;STACK
[1]  ⍝ For use in packaged utility w/s only
[2]  ⍝ Reset SI and run PFK N in the original active w/s
[3]   →('' 11≡QNS 0)/0 ⍝ Quit if in primary name scope
[4]   STACK←'(FIFO'
[5]   A←101 ⎕SVO 'STACK'
[6]   STACK←0
[7]   STACK←')RESET'
[8]   STACK←'PFK ',⍕N
    ∇

Line 3 is there to avoid accidents when the utility workspace is the active work space. This function can be packaged without modification.

Details of Operation

On page 51, it was explained how to put into a load library the load module resulting from the packaging of a workspace. It is also possible, with the assistance of a system administrator, to have the module placed in the MVS link pack area, from where the code can be shared by a number of APL users. When the code is not shared, on the first occasion that an object in a packaged workspace is activated, the packaged workspace is loaded by Processor 11 into the free space in the APL user’s address space. If a saved workspace containing name associations is loaded, then the corresponding packaged workspaces will not be loaded until the respective objects are first accessed.

Functions or operators in the packaged workspace will not consume space in the user’s workspace unless their definitions are modified during the session. However, any variables in the packaged workspace that are referenced or specified during the session will take up space in the user’s workspace, as will any newly defined objects in the name scope of the packaged workspace. If a )SAVE command is issued, then such variables and objects will be saved with the user’s workspace (the packaged workspace itself is not saved). Consequently, to avoid loss of storage space, loss of workspace, and slower loading, the following sequence of commands is recommended when saving an application workspace:

)RESET
)SAVE TEMP
)CLEAR
)COPY TEMP
)WSID xxxx
⎕LX ← 'yyyy'
)SAVE

where the workspace identity and the latent expression are set appropriately. Performing a simple )SAVE may be convenient during testing, but to use such a method to save changes made to objects in a packaged workspace as part of an application is clearly a very poor technique from a theoretical point of view. It is also bad in practice, because if a packaged workspace is modified then the next time that an application accesses it all such changed objects will be lost.

If a packaged workspace has been changed since an application workspace was last saved, then an informatory message is displayed the first time that each associated object is accessed.

Debugging

The chief difficulty in debugging is that, for some reason, stop controls are not honoured in packaged workspaces. Therefore, if tracing does not provide the necessary clues, it may be necessary to introduce a deliberate syntax or value error in the code to persuade the packaged workspace to stop at the desired point. In many applications it will be possible to use Attention during execution to gain access to the name scope of a packaged workspace; in more difficult cases it may be most convenient to have a special function, containing an error, that may be called to force access to the name scope of the packaged workspace.

Using such techniques it is possible to produce local modifications to functions in the packaged workspace, which can be stored using )SAVE. However, because of the way that )COPY works, it is not possible to transfer objects saved in this way to the utility workspace for repackaging, unless one resorts to trickery to define the desired objects in the primary name scope before saving the workspace.

Frequently, when some use of EXP does not have the desired effect, the resulting error message is not very illuminating (as is usual with external routines) as errors in any functions executed are reported against EXP. A bit of head-scratching will often be more effective than trying variations on the original theme.

The best piece of advice for dealing with packaged workspaces is probably: Keep it simple.

Advantages and Disadvantages of Packaged Workspaces

The disadvantages of packaged workspaces are relatively minor:

(i)
There will be a need to keep two copies of functions and operators that require modification when they are packaged.
(ii)
Debugging is more difficult, but problems with this at the program development stage can be avoided by getting things working first in unpackaged mode, and then making any modifications necessary for packaging.
(iii)
When a utility workspace is repackaged, unless accessing workspaces are resaved, there will be informatory messages displayed when objects are accessed.

On the other hand:

(i)
Packaging workspaces is an excellent way of providing a library of utility functions.
(ii)
The writing of workspace documentation functions, and other utilities that need global variables and secondary functions, is greatly simplified by the absence of the need to avoid name conflicts.
(iii)
The number of objects in most application workspaces will be reduced because secondary functions for screen handling, file handling, etc., will be hidden in packaged workspaces.
(iv)
Packaged workspaces may also prove a useful place to store such items as printer identifiers, that have previously been kept on a file.
(v)
Considerable savings in workspace requirements can be achieved, especially if packaged workspaces are placed in the MVS link pack area; it appears also that there are some cpu savings to be had, independent of any resulting from a larger free workspace size.