Chapter 5 : Procedures and Functions
In contrast to many implementations of BASIC, the BBC version of this language has always provided good procedure and function handling, including multi-line definitions and, most importantly, parameter passing. Indeed, programs written in BBC BASIC are normally characterised by their frequent use of both of these structures, leading to highly logical and readable programs. That BBC BASIC encourages such a structured approach to programming is to be commended, and in this respect at least puts this version of BASIC on a part with such languages as C and Pascal.
However, as with some of the other better features of BBC BASIC, the use of procedures and functions has still been limited in certain respects. In particular, parameters could only be used to pass values to a procedure or function, and not for the return of values to the calling program (although a function can, separately, return a single value). Secondly, there has been no provision at all for passing arrays as parameters. Both of these restrictions have disappeared in BASIC V, and a number of other enhancements, particularly with regard to the use of libraries, further improve the power and flexibility of functions and procedures in BBC BASIC. There have also been substantial improvements affecting error handling within procedures and functions, but these are dealt with separately in the next chapter.
Let us consider a very simple example of a procedure or function and see how this benefits from the enhancements to parameter passing in BASIC V. Suppose we want to order the values contained in two variables. Previously, the only way of encoding this would have been to write a function to determine if the two values should be swapped, and then to re-assign the values if necessary. For this assume the two numbers to be re-ordered are contained in variables A and B, and that we wish the larger to be in A and the smaller in B:
IF FNswap(A,B) THEN temp=A:A=B:B=temp : DEF FNswap(A,B) IF B>A THEN =TRUE ELSE =FALSE
It is impossible to keep to a strict use of parameters and LOCAL variables, and perform the swap entirely within the procedure or function definition, because of the inability to return more than a single value, and that only with a function. As a result, the coding above is restricted to ordering the contents of the two variables A and B only. In fact, because of these limitations, the above example might as well be written as:
IF B>A THEN temp=A:A=B:B=temp
but this is quite specific to the variables A and B. In BASIC V we could rewrite this all as a procedure as follows:
PROCswap(fred1,fred2) : DEF PROCswap(RETURN A,RETURN B) IF B>A THEN SWAP A,B ENDPROC
The procedure definition is entirely self-contained and may be used to swap the contents of any two variables, as with fred1 and fred2 in the example call. Note the use of the new SWAP instruction to simplify matters.
The difference is achieved by the use of the keyword RETURN when defining the procedure. Without this, the only option previously, BASIC treats any formal parameters given in the definition of a procedure or function as being strictly LOCAL to that definition. Thus, when such a function or procedure is called, memory is allocated to those variables and the corresponding values, supplied as part of the call, are assigned to them. This explains why it is possible to make assignments to variables defined as formal parameters in a procedure or function definition. Within such a definition, the formal parameters are treated like any other variables such as LOCAL. On exit from the procedure or function, all such variables, and their values, are lost.
When the keyword RETURN is used in specifying a formal parameter, no local allocation of storage takes place, apart from variables explicitly declared as LOCAL, when the routine is called. Instead, the procedure, or function, is passed a reference to the location of the corresponding data item. Any reference within the procedure definition is directed to the original data item as set up by the calling program. Any assignment to such a formal parameter will, therefore, replace the original data with the new value.
There are two points to note when calling a procedure or function which uses RETURN to define parameters in this way. You cannot then specify actual values as parameters in the procedure or function call - only variables will do. This follows logically from the way in which RETURN parameters are treated. Without a variable there is no reference to pass to the procedure or function.
The second point is that variables passes as RETURN parameters must have been implicitly declared, by assigning them some value, before including them in a procedure call. For example, we might wish a procedure to return the largest and smallest values found in an array (see later for more relevant information on arrays). Such a procedure might be defined as:
DEF PROCmaxmin(data(),RETURN max,RETURN min) LOCAL i,n:n=DIM(data(),1) max=data(1):min=max FOR i=2 to n IF data(i)>max THEN max=data(i) IF data(i)<min THEN min=data(i) NEXT i ENDPROC
Note the use of DIM to determine the size of the array passed to the procedure. This technique is described more fully in Chapter Seven. If such a procedure were to be called with a line such as:
in order to find the highest and lowest of a set of heights, then it would need to be preceded by a line similar to:
in order to make the two variables high and low 'known' to BASIC. The values assigned to these variables are quite immaterial, as they are reinitialised within the procedure definition. The purpose is merely to force BASIC to include them in its list of known variables prior to make the procedure call. Arrays (see below) must likewise be dimensioned before being used as parameters to procedures and functions. If these steps are omitted then an error message ("No such variable") will result.
Programmers using BBC BASIC V now have two choices as far as parameter passing to functions and procedures is concerned, call by value (the old method) and call by reference (the new method). Both ave their place in the programmer's repertoire. A clear understanding of the workings and differences between the two methods is, however, essential to avoid subsequent problems in their use.
Passing Arrays as Parameters
In BASIC V, not only has parameter passing to procedures and functions been greatly improved as already explained, but arrays may now also be specified as parameters. As in other instances where complete arrays are referred to in BASIC, an array is specified by its name followed by empty parentheses, thus:
Not only that, but by using the new applications of the DIM statement (see chapter seven), a procedure or function can itself determine the number of dimensions and the size of each dimension of an array passed to it as a parameter.
As an example, consider a simple procedure to sort the elements of an array (assumed one-dimensional) into ascending order. This could be coded as follows:
DEF PROCsort(data()) LOCAL i,j,n:n=DIM(data(),1) FOR i=n-1 TO 1 STEP -1 FOR j=1 TO i PROC swap(data(j+1),data(j)) NEXT j:NEXT i ENDPROC
This procedure uses just a simple bubble sort, which could certainly be improved upon, but it does illustrate the BASIC elements involved. All variables used within the procedure have been declared as local so that procedure is quite independent. The procedure starts by assigning the size of the array to be sorted to the variable n using the DIM statement to obtain this information. The nested FOR...NEXT loops then perform the sort, using the procedure PROCswap described previously, to compare together consecutive elements in the array.
Following our earlier discussion on calling by reference, you might have expected to see the keyword RETURN included in the procedure definition to so define the array parameter. In fact, arrays in BASIC V may only be called in this way, and RETURN is not needed, though it does not generated an error if included. Acorn says that to call arrays by value could involve large amounts of memory being allocated for local storage requirements, and that a significant amount of time might be needed for copying the contents of the array specified into the local array specified by the formal array procedure.
If you do need to write a procedure or function with an array parameter such that the contents of any array passed will not be altered, you will need to make a separate copy before calling the necessary routine and use the unaltered version on exit. Thus, for example:
After executing these two lines, the array A would remain unchanged, while the array temp contains the same data but in ascending order.
In addition to the facility for passing arrays as parameters, they may now also be declared local to a procedure or function, as with other variables. The array must first be declared as local and then dimensioned. There is, however, nothing to stop the array being dimensioned dynamically when the procedure or function is called, maybe using a value passed as a parameter, or by determining the size of another array also passed as a parameter (see Chapter Seven). For example:
LOCAL localarray() DIM localarray(DIM(A(),1),DIM(A(),2))
would declare 'localarray' as a two dimensional local array with the same dimensions as an array A, presumed passed as a parameter. The use of local arrays also provides an alternative solution to the problem of passing an array, as a parameter, to a procedure or function in such a way that the contents of the array passed remain unchanged. The procedure definition could dimension a local array and then copy the contents of the array passed, as a parameter, into this local copy. For example, a procedure definition might begin:
DEF PROCcalculate(data()) LOCAL temp() DIM temp(DIM(data(),1)) temp()=data() . . . . . . .
The procedure would then continue to process the local copy, leaving the original array unaffected. Remember though, that on exit from the procedure, the entire contents of the local copy will be lost, so this is not a suitable solution in all situations.
Since arrays can rapidly eat up memory, it is always sensible to dimension them dynamically where feasible, and to keep the use of arrays, particularly local arrays, to a minimum. Just consider the amount of memory that would be used by a recursively defined procedure repeatedly declaring a two dimensional real array, even one just 10 x 10.
Procedure and Function Libraries
The other main area where handling of procedures and functions has been improved in BASIC V, is in the introduction of a library facility. Once any specified procedure or function library (and both procedures and functions may be included in any library file), has been loaded, any function or procedure calls which cannot be satisfied from within the main program are checked for within the currently loaded library or libraries.
Using libraries has a number of advantages. There are no worries about line number clashes when library procedures and functions are used. Also, keeping a set of routines as a single file, which can be loaded just by referencing its name, saves all the bother of individually loading and merging previously written routines into a new program.
Of course, this convenience has to be paid for in the memory space used up by each loaded library. Because you will seldom use all the procedures or functions in any one library, more memory will be used than if you had individually selected and included just those routines which the program needs.
A BASIC library is simply a saved BASIC program containing only procedure and function definitions, though REM statements may also be included for documentation. Because of the potential for wasting memory space, you are recommended to keep your library files relatively small. It is also essential that you document your library files to make clear what procedures and functions they contain, what their names are and what parameters they require. It is also highly desirable to make such routines as self-contained as possible, by full use of parameter passing and the use of local variables, to avoid any future conflicts or unexpected side-effects.
There are two separate commands which may be used to load a library, and the precise action taken by each is different. The two commands are INSTALL and LIBRARY, and in each case the command is followed by the name of a library, enclosed in double quotes, or a string variable to which a library name has previously been assigned. For example:
INSTALL "Utilities2" LIBRARY library$
The INSTALL command will load any specified library at the top of memory, and move BASIC's stack and the value of HIMEM down. Because of this, INSTALL cannot be used when there is anything on the stack (ie, from within a procedure or function, or inside any kind of loop). In addition, installed libraries may only be removed by quitting BASIC. Libraries loaded with the INSTALL command are relatively permanent in nature, and are est loaded at the very start of a program, or, even better, with a finished application, by including the INSTALL command in an appropriate boot file.
The LIBRARY command, on the other hand, loads a specified library immediately above the program itself, effectively using part of the program's variable storage area. As such, libraries loaded in this way are deleted by any of the commands which clear this area (CLEAR, NEW, RUN, etc). However, the LIBRARY command can be used more dynamically than INSTALL, including its use within procedures and functions which are themselves contained in a library.
A base library may contain a small number of procedures, each of which, when called, loads a further applications-orientated library of additional functions and procedures. This makes for more economical use of memory, but all libraries to be loaded, whether using INSTALL or LIBRARY, must be accessible via the current filing system. For large applications, a hard disc would be most useful.
There are some minor constraints on the use of procedure and function libraries. In particular, they should avoid any line number references as in GOTO or RESTORE. Any such references which are included, will be taken as referring to lines in the 'main' program, with potentially disastrous consequences. References to variables not defined as parameters or local to the procedure should also be avoided.
As a practical aid, the LVAR command will not also show what libraries have been loaded into memory. It does this by listing the first line of each library, and it is therefore highly desirable to make this a REM line containing the library name and any other relevant information. This will then serve to document the library file.
Dynamic Loading of Procedure and Function Libraries
BASIC V under the RISC OS Operating System has been further extended to allow procedure and function libraries to be loaded dynamically on demand. The way to implement this in any program is to declare a string array near the start of the program, and assign to this the names of the relevant libraries (starting with the first element of the array). The system can then be initialised by using the OVERLAY command to specify the name of the array being used as an overlay index. For example:
DIM library$(8) library$(1)="Mathslib.hyp1" library$(2)="Mathslib.hyp2" library$(3)="Mathslib.stats1" library$(4)="Mathslib.stats2" OVERLAY library()
When BASIC encounters the OVERLAY command, it reserves an area of user RAM large enough for the biggest library stored in the specified index array. From then on, when a procedure or function is called, BASIC will first search the current program, then any LIBRARYs, then any INSTALLed libraries, and finally any OVERLAY libraries starting with the OVERLAY area. If the procedure or function called is in an OVERLAY library, then this will be loaded dynamically into the overlay area at that time. Clearly, all OVERLAY libraries must be accessible when running programs using this command.
Because of the action taken by BASIC when the OVERLAY command is encountered it is not possible to change the contents of the index array later, though further library names can be added to the list, provided none of the associated libraries is larger than any originally included.