User-defined functions

From RFO-BASIC! Manual
Manual contents (Statement index)
Language features The basicsArraysData structuresInterrupt routinesUser-defined functions
Interfaces Audio playerBluetoothFilesGraphicsHTMLKeyboardSocketsSQLTelecom
Programming notes Coexisting with Android

User-defined functions (FN functions) work like the built-in functions — ABS(n), MOD(a,b), LEFT$(a$,n), and so on — except that you define what the function does. Defining new FN functions is something like extending the definition of the BASIC language. The FN function contains code that can be called from several places in the program, each time returning to the place from which it was called on that occasion. (The GOSUB is another way to write code to be called from multiple places.)

Here is an example of a user-defined function that takes a single numeric parameter and simply returns a numeric value that is twice the value:

FN.DEF Double(A)

If you write Double(5) anywhere else in your program, this function would refer to 5 as A and return the value 10. That code in your program would work just as though you had written 10 there.

Location. An FN function must be defined earlier in the program than any of the places that call it. Generally, a program consists of all the FN functions, followed by the main program.

Nesting/recursion. An FN function can call another FN function, and can even call itself, or call other functions that call it.

Return value. Every FN function returns a numeric or string value, which can be used as though the value replaced that use of the FN function. For example, you can code an FN function instead of a value supplied to a BASIC statement, in order to use a computed rather than a constant value. You can assign the return value to a variable. You can use an FN function in an expression, to combine the return value with other quantities. Continuing the above example that defined a function Double, this code prints the value of 2E + 5:

PRINT Double(E)+5

You can use the name of an FN function as a BASIC statement of its own. In that case, the function's defined behavior is executed but its return value is not used. In such cases where the return value is unimportant, you can precede the function with the keyword CALL. The following would compute the value 24 but do nothing with it.

CALL Double(12)

Name. When defining an FN function, you specify its name. The rules for naming FN functions are the same as for naming variables. If the function's return value will be a string, its name must end in $. Otherwise, the return value will be numeric.

The formatter capitalizes the names of built-in functions, but does not capitalize the names of user-defined functions. Use of lowercase letters is an easy way to indicate that a function is user-defined with FN.

A single program must not define the same FN function more than once.

You can give an FN function the same name as a built-in function.

  • If you redefine a built-in numeric function, your function overrides the built-in definition. For example, you are free to provide your own definition of MOD(). In this case, your MOD() is called instead of the built-in MOD(), and there is no way to gain access to the built-in function.
  • If you redefine a built-in string function, your function definition is treated as valid but never takes effect. For example, USING$() continues to reach the built-in function even if you write a different function. This is because BASIC evaluates numeric expressions and string expressions differently.


Parameters let an FN function communicate with the code that calls it. Each parameter is one of the following

  1. A numeric scalar, such as N
  2. A string scalar, such as T$
  3. A numeric array, such as A[]
  4. A string array, such as text$[]

The parameters in the function definition correspond to the parameters in each location that calls the function, by their sequence inside the parentheses. The type of parameter in each call must be the type that the function expects. For instance, if the 2nd parameter in the function's definition is a numeric scalar, then in every call to that function, the 2nd parameter must be a numeric scalar (which can be a constant or an expression).

Parameters let the caller pass values to the function. Parameters let the function pass values back to the caller only in the following cases:

  • The parameter is an array, or
  • The caller passes the name of a scalar variable and precedes it with the symbol &.

In these two cases, the function can change the variable and the caller will receive the changed value in the scalar variable that it passed to the function. (In these cases, parameters are "passed by reference" rather than "passed by value.") The return value is another method for a function to pass a value back to the caller.

If a function has no parameters, the parentheses must be present, both when defining and when calling the function.

Variable scope[edit]

With two exceptions noted below, all variables a user-defined function creates are private to it. If you write an FN function that creates a variable V$, then this variable:

  • Is unrelated to any V$ defined outside the function, and
  • Is unrelated to V$ during any previous call to the same function.

That is, the scope of a variable in an FN function is that single call to that single function. When the function returns, all its variables are discarded and the memory they used is reclaimed.

If your FN function creates a variable V$ and calls itself, then the inner function cannot obtain or modify the V$ value set by the outer one. If the inner function creates a variable V$, it will be a new, independent variable.

Data structures

BASIC data structures that programs use with a numeric pointer — such as lists, stacks, bundles, bitmaps, and graphical objects — are global to the BASIC program. If an FN function executes LET P=4, then P survives only until the function returns. But bundle number 4 is the same as it is throughout the BASIC program. So FN functions can read and modify all these data structures.

Interrupt routines

Interrupt routines have access to the global symbol table. When an interrupt routine uses a variable, BASIC looks in the main program, then down the list of every active FN function down to the one that was interrupted, to see if there is a variable by that name. If an interrupt routine needs local variables, their names should be chosen to not conflict with names used anywhere else in the program.

When an interrupt routine calls an FN function, that function has the same access to variables defined outside it for the duration of that call. This is an exception to the general rule.

Warning when using global scope

An FN function with access to variables outside it is a source of programming errors that are hard to locate. If your FN function creates a variable V$, then if given global scope (if called from an interrupt routine, or if global scope is enabled in one of the private versions), then assigning a value to that variable changes the value of any variable named V$ that the mainline had created.


Begins the definition of a user-defined function

FN.DEF name|name$( {nvar}|{svar}|array[]|array$[], ... )

FN.DEF begins the definition of a function. Following FN.DEF is the desired name of the function. If the return value will be a string, then give the function a string name, by appending $. If the return value will be numeric, omit the $.

If the function takes parameters, list the names by which they will be known within the function, surrounded by parentheses and separated by commas. Each parameter can be numeric or string; it can be a scalar (single value) or an array.

You must end the definition of the function, using FN.END, before using FN.DEF again to define another function. Although execution can be nested (the body of one FN function can call others), definitions cannot be nested.

FN.DEF cut$(a$, left, right)
FN.DEF sum(a, b, c, d, e, f, g, h, i, j)
FN.DEF sort(v$[], direction)
FN.DEF pi()

These are examples of single FN.DEF statements, not of complete function definitions. It is illegal for the four examples to actually occur in sequence: This would start the definition of four functions without completing any of them. The final example is a redefinition of a built-in function, in case you really need to change the value of PI().


Returns from a user-defined function

FN.RTN <sexp>|<nexp>

FN.RTN specifies the return value (see the introduction) that the function returns to its caller. If FN.DEF defined a string function (if the function's name ended in $), then FN.RTN must be followed by an sexp. If FN.DEF defined a numeric function, then FN.RTN must be followed by an nexp. FN.RTN must provide a return value; the caller does not have to do anything with it. (Moreover, if the program called the function using CALL, the calling code never sees the return value.)

As well as indicating the return value, FN.RTN performs an immediate transfer of control to the caller. There can be as many FN.RTN statements as desired inside a function (such as in an IF statement). The first FN.RTN executed returns to the caller, and nothing following it in the function is executed on that call to the function.

There does not have to be any FN.RTN statement in a function. In this case, the function returns when FN.END is reached. String functions return the empty string ("") and numeric functions return 0.

Parameters passed by reference are another way for a function to return data to its caller.

A function cannot return a data structure, such as a bundle, but a numeric function can return a number that is the index of a data structure.


The following function is comparable to the built-in MAX() function:

FN.DEF Bigger(A,B)

The immediate return of FN.RTN to the caller means there is no need to use the keyword ELSE.


Executing an FN.RTN does not terminate active loops and blocks. For example, if you code FN.RTN within a FOR/NEXT loop, that loop remains active. If the user-defined function were called from inside another FOR/NEXT loop in the mainline, then when the mainline reached the NEXT statement, NEXT would start the next pass through the still-active FOR/NEXT loop inside the function! This implicit jump back inside a function is never what you meant and is difficult to debug.

If a FOR/NEXT loop computes that the user-defined function can return, it should break out of the loop using F_N.BREAK and only then execute FN.RTN.

If a DO/UNTIL loop computes that the user-defined function can return, it should break out of the loop using D_U.BREAK and only then execute FN.RTN.

It is safe to execute FN.RTN in a single-line IF statement:

IF Result > 0 THEN FN.RTN Result

However, you should not code FN.RTN within a multi-line IF ... [ELSE ...] ENDIF block. The IF statement stores control information in a separate stack, which is not popped off the stack until the ENDIF statement is reached. In this case, there is no danger of an inadvertent jump back into the function, but the unneeded control information is stored for the duration of the program, and the memory used is lost to the program. Eventually, the program could run out of memory.

The GOTO statement (which is used rarely, and never in a routine where high speed is important) includes a superficial check of the DO stack and the IF stack. If either reaches 50,000 items, the program ends with a "stack overflow" message. This message is baffling, as GOTO has nothing to do with stacks. However, it is an alert that the program is not well-structured.


Marks the lexical end of a user-defined function


FN.END is a lexical marker: The code of a user-defined function starts with FN.DEF and ends with FN.END. These keywords must be used in pairs.

If execution of a function reaches FN.END, it does the same thing as FN.RTN using a default value:

  • Numeric functions return the value 0.0.
  • String functions return the empty string ("").


Calls a user-defined function but discards its return value

CALL <user-defined-function>

The CALL statement calls a user-defined function. If the function returns a value, it is not used; to use a return value, do not use CALL but just include the function call in an expression or BASIC statement.

Like LET, the keyword CALL can be omitted. However, including CALL leads to slightly faster execution. In addition, you must use CALL if the start of the name of the function is the same as a BASIC keyword. For example, the Format function might modify its parameter but not return a useful value. You could call it like this:

CALL Format(&A$)

However, omitting the keyword CALL would make the interpreter read this function name as the start of a FOR statement.

Future directions[edit]

Inability of an FN function to gain access to global variables (such as screen geometry or operating modes) is a problem that private versions of BASIC have resolved in different ways. See the sidebar above.