www.riscos.com Technical Support:
Procedures (PROCs) and functions (FNs) provide a way of structuring a program by grouping statements together and referring to them by a single name. The statements can be executed from elsewhere in the program simply by specifying the procedure or function name. A function returns a value, but a procedure does not.
The two structures are very similar, but they are used in slightly different circumstances. PROCs are used wherever a statement can be executed. FNs are used in expressions, wherever a built-in function might be used. Whereas procedures end with an ENDPROC statement, functions return using =expression. The expression is returned as the result of the function call. Functions can return integers, floating point numbers or strings.
Procedure names begin with the keyword PROC, followed by a name. The following shows how a procedure may be defined and called:
10 MODE 12 20 PRINT TAB(0,10)"Countdown commencing "; 30 FOR N% = 30 TO 1 STEP -1 40 PRINT TAB(22,10) " " TAB(22,10);N%; 50 PROCwait_1_second 60 NEXT 70 PRINT TAB(0,10) "BLAST OFF";STRING$(14," ") 80 END 90 100 DEF PROCwait_1_second 110 TIME = 0 120 REPEAT 130 UNTIL TIME >= 100 140 ENDPROC
The important points about procedures are:
Procedures enable you to split up a large amount of code into smaller distinct sections which are easy to manage. The main body of a program can then consist almost entirely of procedure calls, so that it can remain short and easy to follow (since it should be obvious from the procedure names what each call is doing).
Consider the following program:
10 REM Draw boxes centred on the screen 20 MODE 12 30 FOR N% = 1 TO 10 40 PRINT "What size do you want the next box to be "; 50 INPUT size 60 IF size<1024 PROCbox(size) ELSE PRINT "Too large" 70 NEXT N% 80 END 100 DEF PROCbox(edge) 110 RECTANGLE 640-edge/2, 512-edge/2, edge, edge 120 ENDPROC
The procedure PROCbox draws a box around the centre of the screen. The size of this box is determined by the value of the variable edge. This variable has the current value of size assigned to it each time the procedure is called from line 60. The values being passed to the procedure are known as actual parameters. The variable edge used within the procedure is known as a formal parameter.
A procedure can be defined with more than one parameter. However, it must always be called with the correct number of parameters. These parameters may be:
If a string variable is used as a formal parameter, it must have either a string expression or a string variable passed to it. Floating point and integer parameters may be passed to one another and interchanged freely, but remember that the fractional part of a floating point variable is lost if it is assigned to an integer variable. Array formal and actual parameters must be of exactly the same type. That is, if the formal parameter is an integer, then only integer arrays may be passed as actual parameters.
The formal parameters of a procedure are local to that procedure. This means that assigning a value to any variable within the procedure does not affect any variable elsewhere in the program which has the same name. In the following program, the procedure PROCsquare has a parameter S% which is automatically local. It also contains a variable, J%, which is declared as being LOCAL.
10 FOR I% = 1 TO 10 20 PROCsquare(I%) 30 PROCcube(I%) 40 NEXT 50 END 60 100 DEF PROCsquare(S%) 110 LOCAL J% 120 J% = S% ^ 2 130 PRINT S% " squared equals "J%; 140 ENDPROC 150 200 DEF PROCcube(I%) 210 I% = I% ^ 3 220 PRINT " and cubed equals ";I% 230 ENDPROC
In the case of PROCcube, the actual parameter passed and the formal parameter referred to within it are both called I%. This means that there are two versions of the variable, one inside the procedure and another outside it.
Adding the line
35 PRINT I%
to the program above prints out the numbers 1 to 10, showing that the assignment to I% within PROCcube does not affect the value of I% in the main body of the program.
It is good practice to declare all variables used in a procedure as local, since this removes the risk that the procedure will alter variables used elsewhere in the program.
When you declare a local array, the LOCAL statement must be followed by a DIM statement to dimension the local array. For example, consider the following function which, when passed two vectors of the same size, returns their scalar product:
100 DEF FNscalar_product(A(),B()) 110 REM ** Both arrays must have a dimension of 1 ** 120 IF DIM(A()) <> 1 OR DIM(B()) <> 1 THEN 130 PRINT "Vectors required" 140 =0 150 ENDIF 160 REM ** Both arrays must be the same size ** 170 IF DIM(A(),1) <> DIM(B(),1) THEN 180 PRINT "Vectors must be of same size" 190 =0 200 ENDIF 210 REM ** Create a temporary array of the same size ** 220 LOCAL C() 230 DIM C(DIM(A(),1)) 240 REM ** Multiply the corresponding elements and place in C() ** 250 C() = A()*B() 260 REM ** Finally sum all the elements of C() ** 270 =SUM(C())
This example uses a function instead of a procedure. Note that SUM is a built-in function.
Notice that although function definitions may be multi-line, the syntax is such that single line functions as found in older dialects of BASIC may be defined in a compatible manner. Thus you can say either:
1000 DEF FNdisc(a,b,c) 1010 REM find the discriminant of a, b and c 1020 =b*b-4*a*c
or, using the old-fashioned form:
1000 DEF FND(a,b,c)=b*b-4*a*c
(Another limitation of the non-BBC BASIC syntax was that often only single-letter function names were allowed.)
The simple parameter passing scheme described above is known as 'value' parameter passing because the value of the actual parameter is copied into the formal parameter, which is then used within the procedure. The result of any modification to the formal parameter is not communicated back to the actual parameter. Thus the formal parameter is entirely local.
BASIC provides a second method of parameter passing known as 'value-result'. This is just like the simple value mechanism in that the actual parameter's value is copied into the formal parameter for use inside the procedure. The difference is, however, that when the procedure returns, the final value of the formal parameter is copied back into the actual parameter. Thus, a result can be passed back. (This means that the actual parameter can only be a variable, not an expression.)
A statement specifying that you wish to pass a result back for a particular parameter should be preceded by the keyword RETURN. For example:
100 DEF PROCorderedswap(RETURN A,RETURN B) 110 IF A > B SWAP A,B 120 ENDPROC
SWAP is a built-in statement to swap the values of two variables or arrays.
Specifying RETURN before an array formal parameter does not make any difference to the way the parameter is passed.
Arrays are always passed by reference. That is, the array formal parameter acts as an 'alias' for the actual parameter within the procedure or function, and if you change the elements of the formal parameter, the actual parameter will also be altered. If you want to simulate value passing of array parameters, you should use a local array of the same dimensions as the actual parameter, for example:
1000 DEF PROCfred(a()) 1010 LOCAL b() 1020 DIM b(DIM(a(),1), DIM(a(),2)) :REM assume a() is 2D 1030 b()=a() : REM now b() can be altered at will 1040 ...
Because procedures and functions often set up their own error handlers and local data, it is possible to make these local so that nothing outside the procedure or function is affected. In fact, both these may be made 'local' outside of a procedure. For example, you can make an error handler local to a WHILE loop. However, the constructs are mentioned here for completeness. More information can be found about local error handlers in the Error Handling chapter.
To make the current DATA pointer local, and then restore it, a sequence of the following form is used:
1000 LOCAL DATA 1010 RESTORE +0 1020 DATA ... 1030 ... 1080 RESTORE DATA ...
LOCAL DATA stores the current data pointer (i.e. the place where the next READ will get its data from) away. It can then be changed by a RESTORE to enable some local data to be read, and finally restored to its original value using RESTORE DATA. Thus a procedure or function which uses its own local data can read it without affecting the data pointer being used by the calling program.
As mentioned above, LOCAL DATA and RESTORE DATA can be used anywhere that localised data is required, not just in functions and procedures. They can also be nested. However, if LOCAL DATA is used within a function or procedure definition, it must come after any LOCAL variables. BASIC will perform an automatic RESTORE DATA on return from a PROC or FN, so that statement isn't strictly required at the end of PROCs and FNs.
ON PROC IS SIMILAR TO ON GOTO WHICH IS DESCRIBED IN THE Control chapter. It evaluates the expression given after the ON keyword. If the value N% is given, it then calls the procedure designated by N% on the list. For example:
10 REPEAT 20 INPUT "Enter a number ",num 30 PRINT "Type 1 to double it" 40 PRINT "Type 2 to square it" 50 INPUT action 60 ON action PROCdouble(num), PROCsquare(num) 70 UNTIL FALSE 100 DEF PROCdouble(num) 110 PRINT "Your number doubled is ";num*2 120 ENDPROC 200 DEF PROCsquare(num) 210 PRINT "Your number squared is ";num*num 220 ENDPROC
Note, however, that in most circumstances, the CASE statement provides a more powerful and structured way of performing these actions.
A procedure may contain calls to other procedures and may even contain a call to itself. A procedure which does call itself from within its own definition is called a recursive procedure:
10 PRINT "Please input a string :" 20 INPUT A$ 30 PROCremove_spaces(A$) 40 END 100 DEF PROCremove_spaces(A$) 110 LOCAL pos_space% 120 PRINT A$ 130 pos_space%=INSTR(A$," "):REM =0 if no spaces 140 IF pos_space% THEN 150 A$=LEFT$(A$,pos_space%-1)+RIGHT$(A$,pos_space%+1) 160 PROCremove_spaces(A$) 170 ENDIF 180 ENDPROC
In the example above, PROCremove_spaces is passed a string as a parameter. If the string contains no spaces, the procedure ends. If a space is found within the string, the space is removed and the procedure is called again with the new string as an argument to remove any further spaces. For example, typing the string The quick brown fox causes the following to be displayed:
The quick brown fox Thequick brown fox Thequickbrown fox Thequickbrownfox
Recursive procedures often provide a very clear solution to a problem. There are two reasons, however, which suggest that they may not be the best way to solve a problem:
As an example, the following two programs both print Good morning! backwards. The first one uses a WHILE ENDWHILE loop. The second uses a recursive technique to achieve the same result.
10 PROCreverseprint("Good morning !") 100 DEF PROCreverseprint(A$) 110 FOR i% = LEN A% TO 1 STEP -1 120 PRINT MID$(A$,i%,1) 130 NEXT 140 ENDPROC
10 PROCreverseprint("Good morning !") 100 DEF PROCreverseprint(A$) 110 IF LEN(A$) > 0 THEN 120 PRINT RIGHT$(A$); 130 PROCreverseprint(LEFT$(A$)) 140 ENDIF 160 ENDPROC
Functions are similar in many ways to procedures, but differ in that they return a result. BASIC provides many functions of its own, like the trigonometric functions SIN, COS, TAN and RND. If you give RND a parameter with an integer value greater than 1, it returns a random value between 1 and the number given inclusive. For example,
X = RND(10)
produces random numbers between 1 and 10.
You may define functions of your own using the keyword DEF followed by FN and the name of your function. The function definition ends when a statement beginning with an = sign is encountered. This assigns the expression on the right of the sign to the function result. This result may be assigned to a variable in the normal way.
Functions obey the same rules as procedures with regard to naming conventions, the use of parameters and local variables.
We have already seen an example function definition in FNscalar_product above. The following is another example of how a function may be defined and used:
10 FOR N% = 1 TO 10 20 PRINT "A sphere of radius ";N%;" has a volume "; FNvolume(N%) 30 NEXT N% 40 END 100 DEF FNvolume(radius%) 110 = 4/3*PI*radius%^3
Libraries provide a convenient way of adding frequently-used procedures and functions to a BASIC program.
The libraries are kept in memory (unless they are OVERLAYed), and if a reference is made to a procedure or function which is not defined in your program, a search of each library in turn is made until a definition is found. If the routine is found in a library, it is executed exactly as though it were part of the program.
The advantages of using libraries are:
There are three methods of loading a library into memory, INSTALL, LIBRARY and OVERLAY.
INSTALL and LIBRARY are followed by a string giving a filename. This file should contain a set of BASIC procedure and function definitions, perhaps with local DATA statements to be used by those procedures and functions.
INSTALL loads the library at the top of BASIC's memory. It then lowers the upper memory limit that BASIC programs can use. INSTALLed libraries are therefore 'permanent' in that they occupy memory (and may be called) until BASIC is re-started (e.g. by another *BASIC command). You can not selectively remove INSTALLed libraries. INSTALL must be used before the BASIC program is first run rather than from within a program - it is a command, and cannot be used as a program statement.
LIBRARY reserves a sufficient area of memory for the library just above the main BASIC program and loads the library. Any library loaded in this way remains only until the variables are cleared. This occurs, for example, when the CLEAR or NEW commands are given, when the program is edited in some way, or when a program is run. Thus LIBRARY-type libraries are much more transient than INSTALLed ones (as temporary as normal variables, in fact), so you would generally use LIBRARY from within a program.
10 MODE 12 20 REM Print out a story 30 REM Load output library 40 LIBRARY "Printout" 50 REM Read and print the heading 60 READ A$ 70 PROCcentre(A$) 80 REM Print out each sentence in turn 90 REPEAT 100 READ sentence$ 110 REM if sentence$ = "0" then have reached the end 120 IF sentence$ = "0" END 130 REM otherwise print it out 140 PROCprettyprint(sentence$) 150 UNTIL FALSE 200 DATA A story 210 DATA This,program,is,using,two,procedures: 220 DATA 'centre',and,'prettyprint',from,a,library 230 DATA called,'Printout'. 240 DATA The,library,is,loaded,each,time, 245 DATA the,program,is,run. 250 DATA The,procedure,'centre',places,a,string,in,the 260 DATA centre,of,the,screen. 270 DATA The,procedure,'prettyprint',prints,out, 280 DATA a,word,at,the,current,text,cursor, 290 DATA position,unless,it,would,be,split,over, 300 DATA a,line,in,which,case,it,starts,the,word, 305 DATA on,the,next,line,down. 310 DATA 0
The library Printout could be as follows:
10 REM >Printout - Text output library 20 REM ************************************ 30 DEF PROCPrintouthelp 40 REM Print out details of the library routines 50 PRINT "PROCcentre(a$)" 60 PRINT "Place a string in the centre" 70 PRINT "of a 40 character line" 80 PRINT "PROCprettyprint(a$)" 90 PRINT "Print out a word at the current" 100 PRINT "text cursor position, starting" 110 PRINT "a new 40 character line if required" 120 PRINT "to avoid splitting it over two lines" 130 ENDPROC140 REM ******************************* 200 REM Place a string in the centre 210 REM of a 40 character line 220 DEF PROCcentre(a$) 230 LOCAL start% 240 start% = (40 - LEN(a$))/2 250 PRINT TAB(start%);a$ 260 ENDPROC 270 REM ******************************** 300 REM Print out a word at the current 310 REM text cursor position, starting 320 REM a new 40 character line if required 330 REM to avoid splitting it over two lines 340 DEF PROCprettyprint(a$) 350 LOCAL end% 360 end% = POS + LEN(a$) 370 IF end% < 40 PRINT a$;" "; : ENDPROC 380 PRINT 'a$;" "; 390 ENDPROC 400 REM ********************************
In the above example the library Printout contains three procedures:
|PROCPrintouthelp||prints out details of the library structure|
|PROCcentre||prints a string in the middle of a 40 character line|
|PROCprettyprint||prints a word at the current position or, if necessary, on the next line to avoid splitting it|
To make PROCprettyprint and PROCcentre more general-purpose a further refinement would be for them to take an additional parameter specifying the number of characters there are on the line, instead of a fixed length of 40.
OVERLAY enables you to give a list of filenames which contain libraries. When BASIC can't find a PROC or FN within the program or within any of the current libraries, it will start to look in the OVERLAY files. You give OVERLAY a string array as a parameter. For example:
10 DIM lib$(5) 20 lib$()="lib1","lib2","lib3","lib4" 30 OVERLAY lib$() 40 ...
When the OVERLAY statement is executed, BASIC reserves enough space for the largest of the files given in the string array. Then, when it can't find a PROC or FN definition anywhere else, it will go through the list, loading the libraries in order until the definition is found or the end of the array is met.
Once a definition has been found, that library stays in memory (and so the other definitions in it may be used) until the next time a definition can't be found anywhere. The search process starts again, so the current overlay library will be overwritten with the first one in the list. Once BASIC has found a definition, it will remember which file it was in (or more precisely, which element of the array held the filename), so that file will be loaded immediately the next time the definition is required and it is not in memory.
Because of the way one area of memory is used to hold each of the overlay files (and only one at any one time), you are not allowed to call a procedure whose definition is in an overlay library if one of the overlay definitions is currently active. Another way of putting this is that you can't nest overlay calls.
If you know that a given overlay file will never be needed again in the program, you can speed up the search through the overlay list by setting the no-longer-required elements of the array to the null string. You can also add new names to the end of the array, as long as none of the new library files is bigger than the largest one specified in the original OVERLAY statement.
You can execute OVERLAY more than once in a program. Each time it is called, the memory set aside for the previous set of files will be lost, and a new block based on the size of the new ones will be allocated.
There are certain rules which should be obeyed when writing library procedures and functions:
Libraries must not use GOTO, GOSUB, etc. Any reference to a line number is to be taken as referring to the current program, not to the line numbers with which the library is constructed. You can use RESTORE+ to access DATA statements in a library.
It is advisable that library routines only use local variables, so that they are totally independent of any program which may call them.
It is recommended that a library's first line contains the full name of the library and details of a procedure which prints out information on each of the routines in the library. For example:
10000 REM>hyperlib - gives hyperbolic functions. Call PROChyperHelp for details
This last rule is useful because BASIC contains a command, LVAR, which lists the first line of all libraries which are currently loaded. As a result, it is important that the first line of each library contains all the essential information about itself.