Within any system, there are some tasks which are common to many different routines. Some of them are highly abstract, like the task of sorting a list or calculating a factorial number: some are specific to the real world purpose of the system, like running a credit check against a customer code, but either way they may be a necessary part of many different UniVerse Basic programs.
It would, of course, be a waste of time to write the same 'customer credit check' code in every UniVerse Basic program which required it. It would multiply the chance of error, as each implementation of the task would require separate testing, and if any changes were made to the real world credit checking procedure, they would have to be reflected by code changes in many different programs.
It would be better if you could write a single CHECK.CUSTOMER.CREDIT
subroutine which could be accessed by any other program as required. And, of course, you can.
To call an external subroutine, you use the CALL
statement, followed by the name of the subroutine, and a list of 'parameter' values in round brackets and separated by commas. For example:
CUSTOMER.CODE = ORDER.RECORD(4) ORDER.VALUE = ORDER.RECORD(7) CREDIT.OK = 0 CALL CHECK.CREDIT(CREDIT.OK, CUSTOMER.CODE, ORDER.VALUE) IF CREDIT.OK THEN GOSUB FULFILL.ORDER END ELSE GOSUB REJECT.ORDER END
In this routine, the external subroutine CHECK.CREDIT
is called. It is 'passed' three parameters. CUSTOMER.NUMBER
and ORDER.VALUE
are supplied as the information the external subroutine requires to make its decision, and CREDIT.OK
is the variable that the calling routine is expecting the subroutine to assign. The calling routine then bases its choice between fulfilling and rejecting the order on the new value of CREDIT.OK
value.
The subroutine might look something like this:
SUBROUTINE CHECK.CREDIT(RESULT.FLAG, CUSTOMER.ID, CREDIT.REQUESTED) RESULT.FLAG = 1 BEGIN CASE CASE CUSTOMER.ID = 'ACME' ;* No more credit for ACME!! RESULT.FLAG = 0 CASE CUSTOMER.ID = 'GSL' AND CREDIT.REQUESTED > 500 ; * Limited credit to GSL RESULT.FLAG = 0 CASE CREDIT.REQUESTED > 1000 ; * Noone gets more than 1000 RESULT.FLAG = 0 END CASE RETURN END
This isn't a very realistic example: very few companies would hard code volatile commercial data into a subroutine, but it serves to illustrate the principle. As you can see, the CHECK.CREDIT
routine is like any other UniVerse Basic program, except for two things: it begins with a SUBROUTINE
statement, and it ends with a RETURN
,
The SUBROUTINE
statement quotes the name of the subroutine, and lists the parameters which any calling routines have to supply. If you compare the CALL
statement in the calling routine with the SUBROUTINE
statement above, you will see that while the parameter variables have different names, they correspond in number and purpose. In fact, the parameters are passed by position: as long as the RESULT.FLAG is passed as the first parameter by the calling program, it doesn't matter what variable name is used to pass it. The same goes for the other variables.
The RETURN
statement returns control to the statement following the CALL
statement in the calling routine.
Note that the value of RESULT.FLAG
is changed in the subroutine, and that the change is reflected in the value of CREDIT.OK
in the calling routine. The technical term for this is 'passing by reference'. What it means is that the calling routine is actually passing not merely a value '0' as the current value of CREDIT.OK
, it is passing the variable CREDIT.OK
itself. Thus, when any changes are made to RESULT.FLAG
, they are made to CREDIT.OK
in the main program automatically.
The alternative is called 'passing by value', in which only the value is passed, and changes in the subroutine do not affect the main routine. In most programming languages, you are given an explicit choice when you write your calls to subroutines which parameters you will pass by reference and which by value. UniVerse Basic does not do so quite so directly. If you use a single variable name in a parameter position, it will pass it by reference, meaning that the subroutine will have the power to change the value of the variable in the calling routine. If you use a constant or an expression, it will pass by value, preventing any changes in the corresponding variable in the subroutine affecting the calling routine.
However, you can use this knowledge to prevent variables which you don't want changed being passed by reference. Simply turn them into expressions. The simplest expression which evaluates to the value of variable X
is (X)
. By putting brackets round variables in your CALL
, you prevent the subroutine you call changing them.
For this reason, it might have been better to write the CALL
statement in the original example like this:
CALL CALL CHECK.CREDIT(CREDIT.OK, (CUSTOMER.CODE), (ORDER.VALUE))
This is, in fact, what I'd recommend. Not only is it safer, but it adds documentation. Anyone looking at this CALL
can see that you don't plan to have CUSTOMER.CODE
and ORDER.VALUE
changed. However, whether you do this depends on how confident you are that your subroutines never change parameters they shouldn't.
In order to all CHECK.CREDIT
to be called, it must be compiled (like any program) and then 'cataloged': or added to a register of subroutines kept by UniVerse. The most common catalog command looks like this: CATALOG filename programname
, so in this case would have run:
CATALOG BP CHECK.CREDIT
However, only puts the subroutine on a register of subroutines available from the current account. If you are writing a subroutine you wish to make available to programs in any account, you must use a 'glabal' catalog. The syntax is slightly different, as you put an asterisk in front of the subroutine name when cataloging:
CATALOG BP *CHECK.CREDIT
The CALL
statement required to call a globally catalogued subroutine is also slighted different, also taking an asterisk before the subroutine name:
CALL *CALL CHECK.CREDIT(CREDIT.OK, (CUSTOMER.CODE), (ORDER.VALUE))
If you make changes to a catalogued subroutine - whether it is cataloged within the account or globally cataloged - the changes will not become visible to calling routines until the subroutine has been recompiled and recataloged. When you recatalog a globally cataloged routine you will see the following warning:
>CATALOG BP *CHECK.CREDIT "*CHECK,CREDIT" already exists globally. Overwriting this file may affect other users. Do you want to overwrite the existing version? (Y/N) =
Enter a Y
to force the catalog. To prevent the warning appearing, add the word FORCE
to the CATALOG
command:
>CATALOG BP *CHECK.CREDIT FORCE "*CHECK.CREDIT" cataloged.