Chapter 10 - User-defined Functions and Subroutines

True BASIC has three structures that let you break up a program into smaller units: user-defined functions, sub- routines, and pictures. These structures make it easier to write and debug programs because you can build large programs from small pieces. This chapter shows how to define and use your own functions and subroutines. Pic- tures are for graphics only and are discussed in Chapter 13 “Graphics.”

You’ve already seen many of the functions built into True BASIC in Chapter 8 “Built-in Functions.” However, you may not always find a built-in function that calculates the value you need; you may wish to define your own func- tions. Like a built-in function, a user-defined function always returns a single value of a specific type and is unable to change the value of any arguments passed to it. You may use (or “invoke”) a function wherever you may use an expression of the same type. Once the function has been evaluated, its return value is substituted at the place it is invoked.

A subroutine is a block of statements that carries out one or more tasks. A subroutine does not return a value as a function does, but it can return values by changing the values of arguments passed to it. You invoke a subrou- tine with a CALL statement.
Within their definitions, functions and subroutines may invoke themselves. This is known as recursion. Functions and subroutines may be part of your main program, in which case they are internal procedures and
they share all variables with the rest of the main program. Functions and subroutines may also be placed outside
the main program as external procedures whose variables are local or “sealed off” from the main program and any other external procedures.

Defining Functions
——————————————–––—————————————————————————
[ ! ] Note: The keywords DEF and FUNCTION are synonymous throughout the True BASIC language
and may be used interchangeably. In the following discussion, the DEF keyword is used solely for the
sake of simplicity. You may use FUNCTION if you feel that it makes your programs easier to read and maintain.
————————————————–––——————————————————————— You use either a single-line DEF statement or a more complex DEF structure to define your own functions. The simple DEF statement:

DEF Sech(x) = 1 / Cosh(x)
defines the hyperbolic secant function. Once you have defined a function in your program, you may use it in any appropriate expression, such as:

LET factor1 = Sech(-.123)
Each function has a name and may have parameters whose values you supply when you invoke the function. In the example above, Sech is the name of the function and x is its one numeric parameter. When you invoke the Sech function, you must supply a single numeric value as an argument.

Before evaluating the function, True BASIC will assign the value of the argument to the variable used as the para- meter. In the example above the function is invoked with Sech(-.123), where the value –.123 is the argument. True BASIC assigns this value to the parameter variable x in the function definition. Thus, the expression
1/Cosh(x) is evaluated as 1/Cosh(-.123). The value of this expression becomes the return value of the function and is substituted in place of the function invocation in the LET statement. Note that the value of the argument is assigned to the parameter variable; functions cannot change the values of arguments passed to them.
———————————————————–––————————————————————
[ ! ] Note: When variable arguments are passed to a function, the values of those arguments are assigned
to the corresponding parameters within the function definition. True BASIC considers the parameters
in a function definition to be distinct from the arguments; that is, an argument and its matching para- meter are different variables. Because they are different variables, changes to parameters within the function definition have no effect on the values of their corresponding arguments. Thus, functions can- not change the values of the arguments passed to them. This parameter passing mechanism is called passing by value.
————————————————————–––——————————————————— The DEF statement restricts the definition of the function to a single line, which is fine for simple functions. Often, however, more complex function definitions require looping or decision structures that cannot be expressed on a single line, or they may simply be easier to create as a series of steps. For functions longer than a single line, you use a DEF structure. For example:

DEF Gcd(a,b)
! Greatest common divisor
DO
LET r = Mod(a,b)
! Remainder in division
IF r = 0 then EXIT DO
! We are done LET a = b
! Else iterate LET b = r
LOOP
LET Gcd = b
! Value of the function
END DEF

Here Gcd is the name of the function, and a and b are the parameters. A DEF structure begins with a DEF state- ment that names the function and its parameters and ends with an END DEF statement. A LET statement must assign a value to the function name (Gcd) before the end of the definition. Once you’ve defined the function, you may use Gcd with two numeric arguments as a numeric expression. For example:
PRINT Gcd(121,55)
will print the value 11.

The rules for naming functions are the same as those for naming variables. However, you may not use the same name for both a variable and a function. The return value of a function may be either a number or a string; for string functions, the name must end with a dollar sign. Before you may use a function in a program, you must first define it with a DEF statement or structure or you must name the function in a DECLARE DEF statement (see the discussion of external functions later in this chapter).

Parameters may be numeric variables, string variables, or names of arrays. When you invoke a function, the argu- ments you provide must match the parameters named in the DEF statement. True BASIC matches arguments and parameters by the order in which they appear:
DEF abcdef (x, z$, u)
­ ­ ­
LET n = abcdef (3.2, “dog”, y)
Here the value 3.2 is assigned to x, “dog” is assigned to z$, and the value of y is assigned to u. The value of abcdef is then computed according to its definition, and the answer is assigned to n. “Cat” would not be legal as the last argument, since you cannot assign a string to the numeric variable u.

You may also define functions without parameters. For example:
DEF Die = Int(6*Rnd) + 1 ! Simulate one die LET dice2 = Die + Die ! Sum of two dice END
You may exit from the middle of a DEF structure with an EXIT DEF statement. Take care that you assign a value to the function before the exit, or the function will return the default value (0 or the null string).

Arrays as Parameters
Here’s an example of a function that uses an array as a parameter. It finds the largest value in a one-dimensional numeric array:
DEF Max_element(a())
LET largest = a(Lbound(a))
FOR i = Lbound(a)+1 to Ubound(a)
IF a(i) > largest then LET largest = a(i) NEXT i
LET Max_element = largest
END DEF
An array parameter is indicated by empty parentheses, or “bowlegs,” with commas to tell True BASIC how many dimensions that array contains. The number of commas is always one less than the number of dimensions. Thus, () represents a one-dimensional array, (,) a two-dimensional array, and (,,) a three-dimensional array.

When the function is invoked, the use of bowlegs with the array argument is optional. Thus, either of the follow- ing is legal:
PRINT “The highest score is”; Max_element(scores)

PRINT “The highest score is”; Max_element(scores())
as long as scores has been declared as a one-dimensional array in a DIM statement. It is the argument scores that requires a DIM statement, not the parameter a(). Remember that the parameter will be assigned the value of the argument. Therefore, the size of a will be adjusted to reflect the size of scores.
————————————————–––———————————————————————
[ ! ] Note: Since each parameter in a function definition is a separate copy of its associated argument,
arrays as function parameters are not particularly efficient. Each time you invoke a function containing
an array parameter, True BASIC must create a copy of the array passed as an argument. For large arrays, this can require a lot of time and memory. If you need to pass arrays to a procedure, try to use a SUB structure instead — SUB structures pass array parameters by a more efficient mechanism.
——————————————————–––—————————————————————
Recursive Functions
Within a function definition, the name of the function may normally occur only on the left side of the equal sign (=) in a LET statement. That is why we used the temporary variable largest for the computations within Max_element, assigning a value to the function name only at the end of the definition. An exception to this rule is recursion, whereby a function may invoke itself. It is useful in many circumstances, but it is somewhat tricky to master. If you are unfamiliar with recursive programming, you may want to find a book on programming tech- niques or some other resource from which to learn the technique.

One of the most common examples of a recursive function is the calculation of a factorial:
DEF Fact(n)
! Factorial function or n!
IF n = 0 then
LET Fact = 1
ELSE
LET Fact = n*Fact(n-1)
! Recursion
END IF END DEF

If you ask for Fact(7), the value of the function is 7*Fact(6), which invokes the same function to compute Fact(6) and so on. Notice, however, that when the value of the parameter is 0, the function no longer invokes itself, thus ending the “recursive chain” and allowing the previous invocations of the function to be completed in reverse order.

Variables Within Function Definitions
The simplest way to use a function is to define it at the beginning of the program that will use it; in other words, the function definition is “internal” to the main program. If you do this, however, you must be careful with the vari- ables you use within the function definition. Look again at the Max_element function defined earlier in this chapter:
DEF Max_element(a())
LET largest = a(Lbound(a))
FOR i = Lbound(a)+1 to Ubound(a)
IF a(i) > largest then LET largest = a(i) NEXT i
LET Max_element = largest
END DEF
Notice that, besides finding the largest value in the array, Max_element changes the values of largest and i. If the invoking program uses variables by the same name (and the Max_element definition is contained in that program), the program’s variables are also changed — the variables are global to the program and the function definition. Be careful how you use such a function. For instance, if you invoke the function inside a loop that starts with the statement:
FOR i = 1 to 10
chaos will result. Or, if you had previously defined a variable named largest, its value would be changed when- ever this function is invoked.

You can avoid this sort of problem in two ways. First, you could list largest and i in a LOCAL statement within the DEF structure. This would force True BASIC to create separate variables named largest and i for each invocation of the function, preventing any changes to these variables from affecting those outside the DEF struc- ture. Or, you could define Max_element as an “external function” where variables are effectively “sealed off” from the rest of the program. The LOCAL statement and external procedures are described later in this chapter.
Defining Subroutines
Functions are useful, but they’re not ideal for all situations. For even greater flexibility in organizing your pro- grams, True BASIC lets you define subroutines in addition to functions.

Subroutines, unlike functions, do not have a return value. This means that they cannot be invoked as part of an expression; instead, subroutines are invoked with a CALL statement. As you will see below, subroutines also dif- fer from functions in the mechanism used to pass parameters — subroutines may change the values of their argu- ments. The differences between functions and subroutines make each type of structure better suited to certain uses than the other.

In general, functions are most useful when you need to calculate and return a single string or numeric value, and subroutines are most useful everywhere else. Subroutines help you organize your programs by packaging well- defined tasks into discrete structures. This packaging of tasks makes it easy to reuse that code both within the cur- rent program and in future projects. You need to define a subroutine only once but you may call it as many times as necessary.

For example, programs frequently ask questions that require a “yes” or “no” answer. A good program would check whether the answer really was legal and allow the user to type answers in upper or lower case. The following sub- routine meets all of these needs, and can be quite useful:
SUB Yes_no(qu$, ans$) ! Get a “yes” or “no” DO
PRINT qu$; ! Ask the question
INPUT ans$ ! Get the answer
LET ans$ = Lcase$(ans$) ! All lc, easy to check
IF ans$ = “yes” or ans$ = “no” then EXIT DO ! Ok
PRINT “Answer ‘yes’ or ‘no’” ! Else try again
LOOP END SUB
You may invoke this subroutine as follows:
CALL Yes_no(“Shall I go on”, a$) IF a$ = “no” then STOP
The SUB structure begins with a SUB statement that names the subroutine and any parameters, and it ends with an END SUB statement. In the example above, Yes_no is the name of the subroutine and qu$ and ans$ are its two string parameters. When you invoke the Yes_no subroutine with a CALL statement, you must supply a string value as its first argument and a string variable as its second argument.

Let’s examine this distinction between the two arguments a bit further. The first parameter qu$ is an input parameter, which means that the definition of Yes_no uses — but does not change — its value. Thus, if you want the PRINT statement within Yes_no to print anything, you must specify a string value as the first argument when you call the subroutine. Note that the input-parameter value may be supplied as a constant, expression, or a variable; it’s the value that’s important .

The second parameter ans$ is not used until it has been given a value by the INPUT statement within the sub- routine. Its value at invocation is inconsequential; however, its value upon completion of the subroutine contains the user’s answer. Thus, ans$ is an output parameter. You must specify a variable for its argument, so the sub- routine can change its value. When the value of the output parameter is changed the value of its argument vari- able also changes. The terms input parameter and output parameter clarify how the parameter is used — whether it requires a value or a variable as an argument. Note that a single parameter may act as both an input and an out- put parameter, in which case the input value must be supplied as a variable and the value of that variable will be changed by the subroutine.
———————–––————————————————————————————————
[ ! ] Note: When a variable argument is passed to a subroutine, the corresponding parameter is treated as
an equivalent name for the same variable. Because they are the same variable, changes to a parameter
within the subroutine definition will have an immediate and matching effect on the value of its corre- sponding argument — if that argument is a variable. Thus, subroutines, unlike functions, can easily change the values of the arguments passed to them. This mechanism of passing parameters is called passing by reference.
———————————————–––————————————————————————
In the example above, the subroutine is invoked with the statement:
CALL Yes_no(“Shall I go on”, a$)

where the string constant “Shall I go on” is the first argument and the string variable a$ is the second argu- ment. Before it executes the code in the SUB structure, True BASIC assigns the value of the string constant to the parameter qu$ and associates the parameter ans$ with the variable a$. The subroutine prints a prompt using the parameter qu$, and assigns the lowercase equivalent of the user’s response to the parameter ans$. Once it reaches the END SUB statement, the program continues with the statement immediately following the CALL statement. Since the parameter ans$ and the argument a$ are essentially different names for the same variable, the lowercase value stored into ans$ within the subroutine is also the new value of a$.

While a subroutine can modify any variable that is used as an argument in the CALL statement, it cannot mod- ify expressions used as arguments. Thus, arguments such as sum, name$, score(7), and titles$() may be changed, but variables within expressions such as x+3, a$ & b$, and c$[2:4] cannot be changed. Remem- ber that while x is a variable, (x) is an expression. Therefore, if you want to make sure that the value of an input

variable is not changed, enclose it in parentheses. This trick allows you to pass parameters to a subroutine by value rather than by reference.

You may exit from the middle of a subroutine with an EXIT SUB. In the Yes_no subroutine, you could use EXIT SUB in place of EXIT DO.

Arrays as Parameters
As with functions, you may use arrays as parameters in subroutines. Here’s a subroutine that uses a numeric array and numeric variable to accomplish the same thing as the earlier function example that finds the largest ele- ment in an array:

SUB Largest (a(), value)
! Largest value in a list
LET b1 = Lbound(a)
! Find the bounds
LET b2 = Ubound(a)
LET value = a(b1)
! Assume first is largest
FOR i = b1+1 to b2
! But compare to all others
IF a(i) > value then LET value = a(i) NEXT i
END SUB
The same rules apply to specifying array parameters for subroutines as for functions (although the passing mech- anism is distinctly different). Each array parameter must be followed by empty parentheses, or bowlegs. For instance, a() defines a as a one-dimensional array, a(,) defines it as a two-dimensional array, and a(,,) as a three-dimensional array. In the CALL statement, the parentheses are optional. Thus, either of the following is legal:

CALL Largest (prices(), v)

CALL Largest (prices, v)
as long as prices has been previously declared as a one-dimensional array in a DIM statement. It is the argu- ment prices that requires a DIM statement, not the parameter a.

It is more efficient to pass arrays to subroutines than to functions. Arrays are passed to functions by value; the argument array must be copied to the parameter array, which takes time and storage space. With subroutines, however, arrays are passed by reference. Argument and parameter array names are associated to refer to the same array, hence the array is not duplicated. As with scalar variables, subroutines can change the values within argument arrays.

Channel Numbers as Parameters
Subroutines may also use channel numbers as parameters (functions may not). For example:
SUB OpenFile (qu$, #1)
! Open a file PRINT qu$;
! Prompt user INPUT f$
! Name of file CLOSE #1
! In case #1 open OPEN #1: name f$
END SUB

CALL OpenFile(“Data file”, #3) ! Invoke it
The file opened as channel #1 within the subroutine is associated with channel #3 in the calling program. Chan- nel numbers and their uses are described fully in Chapter 12 “Files for Data Input and Output.”

Variables within Subroutine Definitions
As with functions, you must be careful with any non-parameter variables you use within subroutines that are “internal” to (i.e., defined within) the main program. Look again at the Largest subroutine defined earlier in this section:
SUB Largest (a(), value) ! Largest value in a list
LET b1 = Lbound(a) ! Find the bounds
LET b2 = Ubound(a)
LET value = a(b1) ! Assume first is largest
FOR i = b1+1 to b2 ! But compare to all others
IF a(i) > value then LET value = a(i) NEXT i
END SUB
Notice that, in addition to finding the largest value in the array, the definition of Largest changes the values of b1, b2, and i. If the subroutine is part of the program that calls it, those variables are “global” — they are the same as any variables with the same name in the main program. For instance, if you invoke the subroutine inside a loop that starts with the statement:
FOR i = 1 to 10
chaos will result. Also, if you had previously defined a variable named b1 or b2, its value would be changed when- ever this subroutine is invoked.

As with functions, you can avoid this problem in two ways. First, you could list b1, b2, and i in a LOCAL state- ment within the SUB structure, forcing True BASIC to create separate variables named b1, b2, and i for each invocation of the subroutine. Or, you could define Largest as an external subroutine where variables are effec- tively “sealed off” from the rest of the program. The LOCAL statement and external procedures are described later in this chapter.

Functions vs. Subroutines
While functions and subroutines play similar roles, there are some fundamental differences.

A function computes a value and that value is used in an expression. Arguments are passed to functions by value
— the argument’s value is copied to the parameter and the function cannot change the value of the argument itself.

Subroutines carry out tasks and they can change the value of arguments passed to them. Arguments are passed to subroutines by reference; thus an argument variable is essentially the same variable as its corresponding para- meter variable. For instance, in the example above showing an array parameter for a subroutine, Largest sends the answer back via the argument that corresponds to the parameter value.

The Gcd function (defined earlier in this chapter) illustrates the fact that a function does not change the value of its arguments. Suppose that it is invoked as follows:

DEF Gcd(a,b) ! Greatest common divisor
DO
LET r = Mod(a,b) ! Remainder in division
IF r = 0 then EXIT DO ! We are done LET a = b ! Else iterate LET b = r
LOOP
LET Gcd = b ! Value of the function
END DEF
LET a = 121
LET b = 55
PRINT Gcd(a,b); “is the greatest common divisor of”; a; “and”; b
END
It prints the following:
11 is the greatest common divisor of 121 and 55
The arguments a and b still have their original values, even though the definition of Gcd manipulates them as parameters. True BASIC assures this by copying the values into temporary variables.

Generally, if you want to compute a single value, a function is the best choice. This also ensures that the values of the arguments of the function do not get changed because they are passed by value.

Using array parameters for functions may be wasteful, however, since copying a large array takes a lot of time and space. Although the function Max_element and subroutine Largest accomplish the same thing, the subroutine Largest is probably the better choice. In the subroutine — which passes arguments by reference — the array is not copied. Instead, the argument and parameter variables are associated so that they refer to the same original array.

Subroutines provide greater flexibility than functions. Subroutines can carry out a number of tasks and change the values of any number of variables passed to them. You may change any function into a subroutine simply by adding its value as an extra parameter. But the reverse is not true, as the following routine shows:
SUB NextItem (old$, delim$, new$) ! Find next item LET p = Cpos(old$, delim$) ! Next delimiter IF p=0 then LET p = Len(old$) + 1
LET new$ = old$[1:p-1] ! Next item
LET old$ = Ltrim$(old$[p+1:1000]) ! Remove from old$ END SUB
This subroutine identifies items that are separated by delimiters. If delim$ is given the value “ ,;” then items may be separated by spaces, commas, or semicolons. This routine not only finds new$ but also modifies old$. A func- tion could not do this, since functions return only a single value and cannot change the values of their parameters.

As stated earlier, you may exit from the middle of either a function or a subroutine using EXIT DEF or EXIT SUB. However, if you use EXIT DEF, take care that you assign a value to the function before the exit, or the func- tion will return the default value (0 or the null string).

Internal and External Procedures: Global and Local Variables
True BASIC programs may consist of one or more program units, the most important of which is the main pro- gram. The main program includes the entire program up to and including the END statement. Every True BASIC program must contain a main program. Thus, when a program consists of only one program unit, that program unit must be the main program (which is to say that it must end with the END statement).
A True BASIC program may use additional program units in the form of external procedures or modules. This sec- tion discusses external procedures; modules are discussed in Chapter 11 “Libraries and Modules.”
A procedure is a defined function, subroutine, or picture (pictures are discussed in Chapter 13 “Graphics”). An external procedure is not part of another program unit. You may define external procedures in the same file as the main program but after the END statement, or in separate files called libraries (see the next chapter). Each external procedure is a separate program unit.
Procedures that are defined within another program unit (modules excepted) are internal procedures. You may define internal procedures within the main program before the END statement or within external procedures. Each internal procedure is considered part of the program unit in which it is defined. Thus, internal procedures are not separate program units.
You define internal and external procedures in exactly the same way, you invoke them in the same manner, and you use arguments and parameters in the same way to communicate with them. It is their position in relation to other program units that determines whether they are internal or external. There is one important distinction between them, however: variables that are not used as parameters are treated very differently in external and internal procedures. Also, external functions must be named in DECLARE DEF statements within the calling program unit (see the next section on “Internal and External Functions”).
Internal procedures share all variables that are not listed as parameters with the program unit in which they are defined. And since internal procedures may be invoked only within the same program unit in which they are defined, we say that non-parameter variables in internal procedures are global variables, because they are shared throughout the scope of that program unit.

Consider, for example, the following program, in which AddressCode is an internal subroutine defined before the END statement of the main program:
! Create address codes for mailing labels
DIM fnames$(3), lnames$(3) MAT READ fnames$, lnames$ DATA Frank, Peter, James
DATA Hardy, Wimsey, Qwilleran
FOR i = 1 to 3
LET first$ = fnames$(i) LET last$ = lnames$(i)
CALL AddressCode(fnames$(i),lnames$(i),code$)
PRINT “The address code for “; first$; “ “; last$; “ is “; code$ NEXT i
SUB AddressCode(f$,l$,c$)
LET last$ = Ucase$(l$[1:4]) LET first$ = Ucase$(f$[1:3]) LET c$ = last$ & first$
END SUB

END
When this program is run, it gives the following results — which are probably not what the programmer intended!
The address code for FRA HARD is HARDFRA The address code for PET WIMS is WIMSPET The address code for JAM QWIL is QWILJAM
What happened? The first time through the FOR loop, the program assigns “Frank” to first$ and “Hardy” to last$. It then calls the AddressCode subroutine, passing the first elements in the two arrays to f$ and l$, and associating the argument code$ with c$ to receive the address code. When the subroutine ends, the PRINT statement uses the values of first$, last$, and code$. But why doesn’t it print “Frank” and “Hardy” for first$ and last$?
The problem is that the subroutine uses the same variable names first$ and last$ and it changes the values of those variables. Because the subroutine is internal to the main program those variables are shared throughout the program unit. Hence, the PRINT statement, which occurs after the call to the subroutine, uses the changed values for first$ and last$.
Here’s the program reorganized so that AddressCode is an external subroutine, stored after the END statement of the main program:
! Create address codes for mailing labels
DIM fnames$(3), lnames$(3)
MAT READ fnames$, lnames$
DATA Frank, Peter, James
DATA Hardy, Wimsey, Qwilleran
FOR i = 1 to 3
LET first$ = fnames$(i)
LET last$ = lnames$(i)
CALL AddressCode(fnames$(i),lnames$(i),code$)
PRINT “The address code for “; first$; “ “; last$; “ is “; code$
NEXT i
END

SUB AddressCode(f$,l$,c$)
LET last$ = Ucase$(l$[1:4])
LET first$ = Ucase$(f$[1:3])
LET c$ = last$ & first$
END SUB

Now the program gives the intended output as follows:
The address code for Frank Hardy is HARDFRA The address code for Peter Wimsey is WIMSPET
The address code for James Qwilleran is QWILJAM
Even though the subroutine uses the same variable names, any changes to those variables do not affect the vari- ables in the main program. Hence, first$ and last$ retain the values they had before the CALL statement.

This works because external procedures do not treat variables other than parameters in the same way as do inter- nal procedures. All non-parameter variables in external procedures are distinct from those in the program unit that invokes the procedure — even if they have the same name! An external procedure’s variables are created when the procedure is invoked and destroyed when it is terminated. We say that non-parameter variables in exter- nal procedures are local variables, because they are available only within the procedure that uses them — they cannot affect variables in the calling program unit

The next two sections give additional examples of internal and external functions and subroutines and discuss some important practical issues related to using them.

Internal and External Functions
Just as you must define all internal functions before you can use them, you must name all external functions before you can use them. You don’t need to include parameters in the DECLARE DEF statement; you list only the names of the functions. You must always define or declare a function before you can use it, so that True BASIC will know that its name refers to a function and not a variable or array. This is illustrated in the third program exam- ple below.
As noted in the previous section, you must be aware of what variables you use when you write internal procedures, regardless of whether the procedure is a function or a subroutine. As another less obvious example, consider the following internal function that reverses the order of characters in a string:
DEF Reverse$(s$)
! Reverse of string
LET x$ = “”
! Empty string to start
FOR i = 1 to Len(s$)
! A character at a time
LET x$ = s$[i:i] & x$
! Add in reverse order
NEXT i
LET Reverse$ = x$
! Value of function
END DEF

PRINT Reverse$ (“Hello there!”)
GET KEY k
! Hold output until a key is pressed
END
The Reverse$ function in this program correctly returns the following to the PRINT statement:
!ereht olleH

But look at what happens if you use the Reverse$ function as follows to reverse four strings supplied by the user:
DEF Reverse$(s$)
! Reverse of string
LET x$ = “”
! Empty string to start
FOR i = 1 to Len(s$)
! A character at a time
LET x$ = s$[i:i] & x$
! Add in reverse order
NEXT i
LET Reverse$ = x$
! Value of function
END DEF
FOR i = 1 to 4
LINE INPUT PROMPT “Enter a string: “: string$ PRINT Reverse$(string$)
NEXT i
PRINT “All done!” END

Here’s a sample run:
Enter a string: Hello.
.olleH
All done!
What happened? The program is supposed to ask for four strings. The problem is that the Reverse$ function uses two variables that are not parameters: x$ and i. This is a dangerous situation, since either variable might be used by some other part of the program — and indeed the variable i is used by the main program as well.

When the program begins, True BASIC creates the function definition (but doesn’t execute it) and then begins with the FOR loop that uses the function to reverse four strings. This works fine the first time through the loop (where i equals 1). The program gets a string from the user and invokes the Reverse$ function to print that string backwards. The function works properly but notice what it does to i. When Reverse$ finishes it has changed the value of i to the length of the string argument plus 1 (or in this example 7). (It has also changed the value of x$ but x$ isn’t used anywhere else in the program.) Thus, when True BASIC executes the NEXTstate- ment after the PRINT statement, it increments i to 8 so the main FOR loop ends. The program prints the final statement and stops.

Now let’s look at this same program rewritten with Reverse$ as an external procedure:
DECLARE DEF Reverse$

FOR i = 1 to 4
LINE INPUT PROMPT “Enter a string: “: string$ PRINT Reverse$(string$)
NEXT i
PRINT “All done!” END
DEF Reverse$(s$)
! Reverse of string
LET x$ = “”
! Empty string to start
FOR i = 1 to Len(s$)
! A character at a time
LET x$ = s$[i:i] & x$
! Add in reverse order
NEXT i
LET Reverse$ = x$
! Value of function
END DEF
We did just two things to make this change. We moved the function definition to follow the END statement of the main program (we could have moved it to a separate file). And we added the DECLARE DEF statement at the beginning of the main program to tell True BASIC that Reverse$ is a function that is defined elsewhere.

When you run the revised version of the program, it works as expected:
Enter a string: Hello.
.olleH
Enter a string: Able was I I saw elbA
Enter a string: ere ere
Enter a string: I saw Elba!
!ablE was I All done!
When Reverse$ is an external function, all variables used in the definition are local to that definition only. The function’s variables are totally separate from those in the rest of the program, even if they happen to have the same names. The only information shared with the main program is the string argument passed to the function’s para- meter and the string value returned by the function.

Internal and External Subroutines
As you’ve seen, all variables in an internal subroutine are shared with the rest of the program. Variables in an external subroutine are local to that subroutine; the only shared values are those passed between arguments and parameters.

To create an external subroutine, you simply place it after the END statement or in a separate file. There is no equivalent to the DECLARE DEF statement. Because you must invoke a subroutine with a CALL statement, it is always clear to True BASIC that you are referring to a subroutine and not a variable or array.

Although you can use global variables to share information between your program and internal subroutines, we recommend that you use parameters to clearly indicate information passed to and from your subroutines. For example, the following program uses an internal subroutine to get an answer from the user, check it for correct- ness, and respond appropriately. Although it uses no parameters, it works because all variables are shared with the rest of the program:
!State quiz

DIM questions$(10), answers$(10) LET count, correct = 0
DO WHILE MORE DATA
LET count = count + 1
READ questions$(count), answers$(count)
LOOP
FOR i = 1 to count
DO
PRINT questions$(i) CALL Get_answer
LOOP UNTIL correct = 1
NEXT i
SUB Get_answer
INPUT ans$
IF Lcase$(ans$) = answers$(i) then
PRINT “Correct!”
LET correct = 1
ELSE
PRINT “Wrong, try again...”
LET correct = 0
END IF
END SUB
DATA “What is the 50th state”, hawaii
DATA “What is the largest state”, alaska
DATA “What state has the nickname ‘Old Dominion’”, virginia

END
There are two potential problems with this program, however. The subroutine would not work if you later decide to make it an external procedure that can be shared with other programs. Also, if the above statements were part of a lengthier program, it might not be clear where the program sets the value for correct — used as the exit condition for the DO loop.

You are generally better off using parameters for any values you need to share between the program and the sub- routine. For example, you could rewrite Get_answer to use two parameters — a string variable and a numeric variable. Notice that the string argument supplied in the CALL statement is an element of the string array answers$; a single element in an array is treated as a single variable.

Consider the following variation:
...
FOR i = 1 to count
DO
PRINT questions$(i)
CALL Get_answer(answers$(i), correct)
LOOP UNTIL correct = 1
NEXT i

SUB Get_answer(a$,c) INPUT ans$
IF Lcase$(ans$) = a$ then
PRINT “Correct!”
LET c = 1
ELSE
PRINT “Wrong, try again...”
LET c = 0
END IF
END SUB
...
This version of the subroutine will now work equally well as an external subroutine. Also, it is clear from reading the program code which values are shared with the subroutine. The first argument supplies the correct answer to the subroutine. After it carries out its tasks, the subroutine passes back an appropriate value to the argument correct.

Advantages and Disadvantages of External Procedures
When you use external procedures, you don’t have to worry about duplicating variable names. This is particularly important if you wrote a routine a long time ago and have forgotten the names used within it, or if someone else wrote it. To use an external procedure you need to know: (1) its name, (2) what kind of arguments it takes, and (3) what it does. You do not need to know how it is programmed or what variables are used.
You may store external procedures in separate “library files” (see the next chapter). You can therefore use the same procedure from any number of programs by referring to the library file. Thus, you need not copy or rewrite commonly used code into each of your programs.
The price for this protection is that the values of local variables are lost when an external function or subroutine terminates and all local channels are closed. As a consequence, such external procedures cannot ‘remember’ what they did the last time they were called. The next chapter describes “modules” which give you much greater control over the scope of variables, letting you create procedures that can remember the values of local variables and define items available to more than one program unit.

The LOCAL Statement
The LOCAL statement serves two purposes. You may use it to indicate variables or arrays that are to be local to an internal procedure and not available outside that procedure. You may also use it in combination with an OPTION TYPO statement to catch misspelled variable names.

The LOCAL statement is helpful in internal procedures when you want to keep variables separate from the rest of the program. For example, in the internal subroutine:
SUB Xyz
LOCAL i

FOR i = 1 to 10
LET sum = sum + i
NEXT i
END SUB

the variable i is local, and hence considered different from i in the rest of the main program. In effect, i is treated as if Xyz were an external routine, yet sum is available to the main program. Clearly this option is useful for assur- ing that a temporary variable like i does not conflict with one in the rest of the program.

You may also list arrays — with their dimensions — in a LOCAL statement. Because you must give the array dimensions, the LOCAL statement takes the place of a DIM statement for those arrays.

An OPTION TYPO statement tells True BASIC to alert you if it finds an incorrect variable name. You must declare all variables that first appear in your program or program unit after the OPTION TYPO statement. True BASIC uses the declared names as a “dictionary” of acceptable names; it also accepts any variable names used before the OPTION TYPO. If True BASIC encounters any other variables, such as a misspelled name, an error occurs. For example, if you attempt to run a program that begins as follows:
OPTION TYPO
DIM grades(10)
! or LOCAL grades (10) LOCAL name$, sum, average

INPUT PROMPT “Student’s name: “ : names$ PRINT “Enter grades separated by commas.” MAT INPUT grades
...
True BASIC will halt at the misspelled names$ variable on the INPUT PROMPT statement and print the mes- sage “Unknown variable.”
For this to work best, you should place the OPTION TYPO statement early in your program unit and then declare all your variables. You declare variables with LOCAL statements, arrays with LOCAL or DIM statements, and parameters by placing them in the SUB, DEF, FUNCTION, or PICTURE statements defining procedures (for information on picture procedures see Chapter 13 “Graphics”). You may also declare variables using the PUBLIC, SHARE, and DECLARE PUBLIC statements described in the next chapter.

You may use the OPTION TYPO statement in any program unit or module header (see the next chapter). Once used, it applies to all the variables that first appear after the OPTION TYPO statement, and it remains in effect until the end of the file that contains it. Note that OPTION TYPO is technically not an executable statement — it is used only when True BASIC compiles the program. For this reason, its effects are related to its position in the file, not to its position in the flow of control. Thus, when the OPTION TYPO statement appears in an external procedure stored after the END statement of the main program, it applies only to the remainder of that procedure and any other procedures that are stored after it in the file. It does not apply to any portion of the file that precedes it — including those portions of the main program that follow the invocation of the procedure containing the OPTION TYPO statement.