4.1 Use of the Internal Set Index

Using @ to  Reference the Current Element of a Set     

Sets have an internal index which, once set, makes it possible to refer to set and table elements using the symbol @. This is supposed to be more efficient than referring to the set or table element by an explicit value or a variable. Thus you may see code such as:

IF      ?ORDITEM IN ORDITEM
AND     :TONNES = ORDERS(@,TONNES)
AND     :COLOUR = ORDERS(@,COLOUR)
AND     :CUST = ORDERS(@,CUSTOMER)
. . .
THEN    GETDATA ?ORDITEM

Here ORDITEM is the rowset for ORDERS and ?ORDITEM is an input argument to the rule, so this code positions ORDITEM at ?ORDITEM and then sets up the globals :TONNES, :COLOUR, etc from values in the table ORDERS. Although this is indeed more efficient than

IF      ?ORDITEM IN ORDITEM
AND     :TONNES = ORDERS(?ORDITEM,TONNES)
AND     :COLOUR = ORDERS(
?ORDITEM,COLOUR)
AND     :CUST = ORDERS(
?ORDITEM,CUSTOMER)
. . .
THEN    GETDATA ?ORDITEM

the gain is very small because once the interpreter has found ?ORDITEM in set ORDITEM for :TONNES it begins its search to resolve the second ?ORDITEM where it found the first one. The search is therefore over straight away and the time-saving is minimal.

Disadvantages of Using @

Set against this minimal time-saving are several disadvantages:

  • the source code is opaque, because it is not immediately obvious that ORDITEM is the rowset for ORDERS and that the statement ?ORDITEM in ORDITEM is positioning the rowset correctly;

  • the TRACE output shows the @ symbol as the value of the rowset for ORDERS rather than the actual value, thus making debugging harder;

  • if the rule happens to be called with ?ORDITEM FREE, it becomes a loop over ORDITEM (this has happened – see the next section for recommendations about fail-safe coding) whereas the direct code would generate an immediate error and stop the interpreter;

  • it reduces the modularity of the code by relying on the setting of an internal index which may be occurring elsewhere. Suppose, for instance that someone decides to set up another table CUSTDATA (customer data) whose rowset is CUSTITEM which is a subset of ORDITEM. He changes ORDERS(@,CUST) to CUSTDATA(@,CUST). The code appears to continue to work but in fact it is using the setting of the internal index in CUSTITEM from a different rule. The direct code retains its modularity and works or fails according to whether it is called for an ?ORDITEM which lies in CUSTITEM.

Using @ in Loops

One place where the use of @ is justified is in loops. A set may contain more than one occurrence of a particular string. In such a case, using @ ensures that the current element in the loop is referenced. A standard use of this is to find the internal index of the loop variable and thereby to identify when the loop is on its last iteration. This allows you to keep code in a single rule which lies naturally together:

        /* Work out the size of the set and the index of its last element */

IF      ?SIZE = SIZEOF MYSET
AND     ?SIZEM = ?SIZE - 1

        /* Loop over the set unless it's null */

AND     ( ?SIZE EQ 0
           OR ?LOOP IN MYSET
                  AND ?INDEX = INDEXOF MYSET(@)

                  AND <processing for ?LOOP>

        /* If it's the last element, do extra processing */

                  AND ( ?INDEX LT ?SIZEM
                         OR <extra processing>
                       )
          )

THEN    <predicate>

Using @ in Nested Loops over the Same Set

One pleasant feature of the internal index is that the index for a loop over a set is maintained separately from that for the set itself. The set index holds the most recent value of the internal index of the set while the loop index remains unchanged until the next iteration of the loop. At this time the loop index is moved on to the next element of the set, regardless of what has happened to the set index in the meantime.

This means that you can have nested loops over a single set and can change the index of the set within the loop separately from its value in the loop. The following code is therefore acceptable and will work the way you wish it to:

IF      ?LOOP1 IN MYSET
AND         ?I1 = INDEXOF MYSET(@)
AND         ?LOOP2 IN MYSET
AND             ?I2 = INDEXOF MYSET(@)
AND             ?I2P = ?I2 + 1
AND             MYSET(INDEX) = ?I2P


Back                                Next
Comments