Back To Basics - Part 8

In part eight of his series on Basic programming, Mark Moxon takes a look at functions, and the ins and outs of local variables

Procedures and functions are very similar, so perhaps a really quick recap of what they are would be a good idea, just to save you fishing out last month's copy. A procedure is simply a block of code which we start with a line like:

DEF PROCmove

and end with the line:

ENDPROC

This block of code can be executed (or called) by including the statement:

PROCmove

in our program. Simple really, but very powerful.

Functions are a similar breed in the sense that they are blocks of code which can be executed, but they take a different form and have a different purpose. Functions calculate and return values, and are used as parts of expressions which need to be evaluated, rather than stand-alone statements. Here's a simple example.

Don't worry about how we actually define a function for a second; just assume that we have defined a function called FNsquare(n), which takes a number and returns the value of that number squared - in other words, multiplied by itself. Function names always start with FN, in the same way that procedures always start with PROC, and the way we can call functions is like this:

num=FNsquare(3)

Assuming we have written our code for FNsquare(n) properly, this will assign the variable num to have the value of 3 squared, or 9. Because a funtion call acts like any other expression and can be evaluated, we can include them in more complicated expressions like:

num = (FNsquare(a)-FNsquare(b))/C

which would set num to be a-b divided by c.

Defining function

Function definitions take the same sort of form as procedure definitions: they are blocks of code which are executed only when called. We start a function definition with a line like:

DEF FNsquare(n)

in a similar way to DEF PROC for procedures.

However the last line of a function definition takes a rather strange form: the line starts with an equals sign, which is then followed by the value to be returned as the value of the function. Thus our FNsquare definition would be:

DEF FNsquare(n)
=n*n

This simply means that when the function is called, the variable n is set to the value in the brackets; for example, n is set to 3 if the function is called by FNsquare(3). The value returned is then that of n multiplied by itself, which is the square of the argument supplied to the function.

A quick example might help to clarify matters. Have a look at Listing 1, which shows how functions can be used together. Quite often in Wimp programming you need to take a full filename, such as 'adfs::Mark.$.tmp.TextFile', and split it into the filename ('TextFile') and the pathname ('adfs::Mark.$.tmp'). Listing 1 does just that: run it and type in a typical full filename, and the split will be printed. The two different parts of the string you enter are calculated by the functions FNfilename and FNpathname. Let's take a look at FNfilename first, the function which pulls off the last word in the string.

Listing 1

REM >Listing 1
:
ON ERROR REPORT:PRINT " at line ";ERL/10:END
MODE 0
INPUT "Please enter a full filename",f$
PRINT "Filename = ";FNfilename(f$)
PRINT "Pathname = ";FNpathname(f$)
END
:
DEF FNfilename(file$)
  temp%=0
  REPEAT
    pos%=temp%+1
    temp%=INSTR(file$,".",pos%)
  UNTIL temp%=0
=MID$(file$,pos%)
:
DEF FNpathname(file$)
  filelen%=LEN(FNfilename(file$))
  pathlen%=LEN(file$)-filelen%-1
=LEFT$(file$,pathlen%)

The idea is that the REPEAT-UNTIL loop steps through each occurrence of a full stop in the string, and then the last line (the part which returns the value of the function) simply takes the part of the string from the last full stop onwards, and returns this as the result of the function.

The code works like this: set temp% to point to the start of the string, and pos% to the character after this. Now look for a full stop in the string: if one exists then the INSTR function will return the position of this full stop, so set temp% to that position, set pos% to one after this position, and go back, searching the string from position pos% onwards.

Keep doing this until we don't find a full stop (when INSTR returns 0), and then temp% will point to the position of the last full stop, so taking the string from position pos% onwards will give us the filename.

We could use a similar method to find the pathname, but a quicker method is to use the filename function already defined. If we consider the whole string and we know the filename, then to get the pathname all we have to do is chop off the right number of characters from the end of the string.

So FNpathname works like this: set filelen% to the length of the filename part of the string, and then use this to calculate the length of the pathname.

The calculation is simple, and is just the length of the whole string, minus the length of the filename (filelen%), minus 1 to account for the full stop between the filename and the pathname. The final line of the function definition returns the first pathlen% characters from the left of the string: in other words the pathname.

Local variables

One of the benefits of procedure and function definitions is the ability to copy them from program to program and know that they will still work properly. For example, the functions described above can be used in all sorts of programs, and as they are self contained you don't have to change them to work with different programs.

However, there can be occasions where using the same procedures in different programs can cause problems. Take FNfilename for example: it defines two variables, temp% and pos%, while calculating the filename from the string file$.

This doesn't cause any problems in Listing 1, but imagine a different program which calls this function, and already has a variable defined called pos% which is used elsewhere in the program.

If FNfilename is called in this program, then the value of pos% is changed by the function, overwriting the previous value and potentially causing havoc. One way round this is to make sure that different names are used for the two variables, but a better solution is to make pos% 'local' to FNfilename.

When a variable is made local to a procedure or function, then addigning values to that variable won't affect any variable of that name in the rest of the program.

Essentially, what happens is that when the procedure is called the computer remembers the value of pos% (and any other variables which are made local), and when the procedure terminates (via ENDPROC, or in the case of functions via the equals sign) the value of the variable is restored. In this way we can write routines which will work in any program in exactly the same way.

The command to make a variable local has the following form:

LOCAL pos%,temp%

where the variables listed (separated by commas) are all made local, so changing their values in the procedure won't affect variables with the same name which are already defined. This line is normally put straight after the line defining the routine (the DEF line).

Listing 2 contains an example of the effect of using LOCAL. It contains two identical procedures, the only difference between them being the inclusion of a statement which makes the variable var% local to PROClocal.

Listing 2

REM >Listing2
:
ON ERROR REPORT:PRINT " at line ";ERL/10:END
MODE 0
var%=5
PRINT "At start of program, var% = ";var%
PROClocal
PRINT "After PROClocal, var% = ";var%
PROCno_local
PRINT "After PROCno_local, var% = ";var%
END
:
DEF PROClocal
  LOCAL var%
  var%=10
  PRINT "Inside PROClocal, var% = ";var%
ENDPROC
:
DEF PROCno_local
  var%=10
  PRINT "Inside PROCno_local, var% = ";var%
ENDPROC

If you run the program, you will see that var% is set up to be 5 in the main body of the program, and each of the procedures changes that value to 10. As you can see from the results of running the program, if var% is 5 and PROClocal is called, then var% changes to 10 inside the procedure, but when the program returns from PROClocal, the original value of 10 is restored. However, if var% is 5 and PROCno_local is called, then var% changes to 10 inside the procedure as before, but retains its value of 10 when the procedure returns.

It's a good habit to make variables local if they are only to be used inside that procedure or function: then you know you won't have variable clash. Another interesting effect of procedures is that variables passed as arguments are always local. To find out how you can change this, tune in next month as we finish off procedures and functions.


Source: Acorn User 140 - March 1994
Publication: Acorn User
Contributor: Mark Moxon