The principle of DRY has been around for sometime. When applied to the PICK/U2 environment, there are two main methods of implementation via routines – internal subroutines (GOSUB) and external subroutines (SUBROUTINE/FUNCTION).
But the relationship between these two methods and in particular between SUBROUTINE and FUNCTION is rather interesting.
The internal subroutine GOSUB is really just a hacked GOTO with an automatically stored jump back or RETURN.
The mechanism of use is to call the subroutine via the GOSUB statement with a label which refers to the code point to transfer to. Ending and exiting the subroutine is via the RETURN statement.
program XYZ ... gosub ReadStuff: ... stop ReadStuff: *Routine to Read stuff ... return
The internal subroutine has no local scope capabilities and if not carefully written, variable naming can become an issue as they are global within the complete routine.
But the GOSUB can very handy when the processing and code logic becomes large and repeatable. I also think that the ON GOSUB is rather neat. I have ignored the RETURN TO statement, as like a GOTO, it is just plain evil.
The external subroutine on the other hand consists within it’s own right as a compiled and cataloged routine and comes within two forms – SUBROUTINE and FUNCTION.
The subroutine is a separate routine which has been compiled and cataloged and that can be called by any other routine by the CALL statement. The routine must start with a SUBROUTINE statement which provides a name and a list of parameters that will be passed:
subroutine TEST(VAR1, VAR2) ... return
program XYZ ... call TEST(TEST1, TEST2) ... end
Strangely enough, how the variables are passed is governed by the calling process, not the SUBROUTINE definition. By default the parameters are passed by reference; that is, the pointers to the parameter variables are passed and the values are not copied. Hence, within the SUBROUTINE, any changes to the passed variables will be reflected within the calling routine. This method is used as it is faster at transferring variables than by copying a value, where each parameter must be copied to a new variable, especially if a large number of parameters are to be passed or if the parameters are arrays.
There are two common methods of dealing with this situation: define the passed parameters as pass-by values (enclose within brackets) or copy the parameters to new variables within the subroutine:
program XYZ ... call TEST((TEST1), (TEST2)) ... end
subroutine TEST(VARTEMP1, VARTEMP2) VAR1 = VARTEMP1 VAR2 = VARTEMP2 ... return
With the SUBROUTINE, any return values are transferred back via one of the calling parameters. So, I prefer to use method two, copying around the input values as it is not really appropriate to have this controlled by the calling routine.
program XYZ ... call TEST(INVAR1, INVAR2, RESULT) ... end
subroutine TEST(INVARTEMP1, INVARTEMP2, RESULT) INVAR1 = INVARTEMP1 INVAR2 = INVARTEMP2 ... return
The FUNCTION is similar to the SUBROUTINE except that a value is returned by the RETURN statement and the calling process is similar to a normal function with UniBasic.
The routine must be defined as a function by the FUNCTION statement on the first line of the routine and within the calling routine, the function name must be defined as a function via the DEFFUN statement:
function TEST(VAR1, VAR2) ... return(RESULT)
program TESTFUN deffun TEST(VAR1, VAR2) ... result = TEST(VAR1, VAR2) ... end
The parameters within the DEFFUN statement are dummies, and are only used to define what type of data is being transferred (string or array, for example). The interesting part is that there is an ‘invisible’ variable created within the function parameter list for the return value. It is placed as the first variable within the argument list. This is equivalent to:
function TEST(<em>RESULT</em>, VAR1, VAR2)
The use of the SUBR statement is similar to the FUNCTION except the name of the function to call is passed as the first parameter. Also, the returned parameter must be the second parameter within the list.
There is a lengthy description within the U2documentation – the UniBasic Reference Manual – about how to structure a SUBROUTINE to work with the SUBR.
So, can a function and a subroutine be interchangeable when used with a SUBR? Yes, if the subroutine follows the return-as-first-parameter rule.
For example, here are a subroutine and function:
subroutine TESTSUB(RESULT, PARAM1, PARAM2) RESULT = "TESTSUB = " : PARAM1 + PARAM2 return
function TESTFUNC(PARAM1, PARAM2) RESULT = "TESTFUNC = " : PARAM1 * PARAM2 return(RESULT)
program TEST rem Define CONSTANTS equate EMPTY.STR to "" print subr("TESTSUB", 2, 3) print subr("TESTFUNC", 2, 3) deffun TESTSUB(PARAM1, PARAM2) print "DEFFUN = " : TESTSUB(1,2) RESULT = EMPTY.STR call TESTFUNC(RESULT, 3, 4) PRINT "FUNC AS SUB = " : RESULT end
and the output will be:
TESTSUB = 5 TESTFUNC = 6 DEFFUN = TESTSUB = 3 FUNC AS SUB = TESTFUNC = 12
The SUBROUTINE and FUNCTION are interchangeable as long as the parameters adhere to the structure of the result variable as the first variable in the SUBROUTINE list.
As the SUBR is normally used within a derived field, it is possible to write one routine that can be used within a UniBasic program and within a dictionary field (derived) definition without any changes. This is an important concept that is often overlooked within U2.
And yes, if you had noticed, everything becomes a function! Is this so bad?
This can also be extended into SB+ by the use of a small PARAGRAPH to wrap the CALL statement. And as I have mentioned before, SB+ is a highly functional paradigm.
So, back to DRY – Don’t Repeat Yourself – use one routine for each of your business logic and processes, in all places within U2.