Global and Local Variables in SQR | KEVIN RESCHENBERG 03-14-2004 |
One more SQR item and then we'll return to other topics starting next week.
Today I'd like to deal with the somewhat confusing topic of variable scope in SQR—something I listed as one of the
frequent sources of SQR bugs.
SQR provides for two types of variable scope: global and local. Here are the rules:
A local procedure is any procedure that has one of the following in its BEGIN-PROCEDURE: Parameters (coded within parentheses),
or the keyword LOCAL.
Within a local procedure, a variable name containing an initial underscore (#_amount) indicates a global variable.
Within a local procedure, a variable name without an initial underscore (#amount) indicates a local variable.
A local variable cannot be accessed from outside of the LOCAL procedure in which it is used, unless the procedure passes the variable to another local procedure through a parameter list.
Within a global procedure, all variables are global, and a variable name cannot contain the initial underscore.
A local procedure can return a value to a global variable by specifying an initial colon (:#amount) within its parameter list.
These rules apply to numeric (#), string ($), and database column (&) variables (but not arrays).
Maybe a few examples will help.
begin-procedure Main
let #n = 1
do Other-Procedure
show #n
end-procedure
begin-procedure Other-Procedure local
let #n = 2
end-procedure
What is the value of #n at the SHOW statement? It is 1. The assignment within Other-Procedure (a local procedure) creates another variable called
#n. There is no connection between this local #n and the global #n.
If, however, Other-Procedure contained this line:
let #_n = 2
then the value of the global variable #n would be changed to 2, and the SHOW would display 2.
Now consider this:
begin-procedure Main
let #n = 1
do Other-Procedure(#n)
show #n
end-procedure
begin-procedure Other-Procedure(:#number)
let #number = 2
end-procedure
This time, the value of #n at the SHOW statement is 2. #n was passed as a parameter to Other-Procedure, which knows it as #number.
In addition, Other-Procedure's parameter list includes the leading colon (:#number), indicating that it will pass a value back to the
caller. Since the caller provides the variable (#n), it will receive the modified value of this variable, which is 2.
Be careful to code the leading colon if you need it. Suppose the BEGIN-PROCEDURE was coded like this:
begin-procedure Other-Procedure(#number)
In this case, #number would contain the value of #n passed by the caller (1) but it would not be passed back to the caller.
Even though Other-Procedure changes the value of #number, this will not affect the value of #n, since the leading colon is not coded.
Here's a trick question. What's the value of #n at the SHOW statement in the example below?
begin-procedure Main
let #n = 1
do Other-Procedure(#n)
show #n
end-procedure
begin-procedure Other-Procedure(#n)
let #n = 2
end-procedure
It's 1. The leading colon was not coded, so the #n in the local procedure is not passed back to the caller.
How about in this convoluted example?
begin-procedure Main
let #n = 1
do Other-Procedure(#n)
show #n
end-procedure
begin-procedure Other-Procedure(#n)
let #_n = 2
end-procedure
This time it's 2. The value of global #n was passed to the procedure as #n, but was never used. Instead, the procedure specified the
global variable itself directly, as #_n. Remember that #n in a global context (in procedure Main in this example) is the same
as #_n in a local context (in Other-Procedure). As I said, this example is convoluted, but it's the sort of thing we end up
with if we're not careful.
Now try this example:
begin-procedure Main
let #n = 1
do Other-Procedure
show #n
end-procedure
begin-procedure Other-Procedure
let #_n = 2
end-procedure
This example is invalid and it won't even compile. The reason is that #_n is specified within a global procedure, and that's illegal. Other-Procedure
is global in this example because it does not receive any parameters and does not contain the LOCAL keyword.
So, with these complications, why use local procedures and variables at all? They give you much better control over variables. If you break
your program up into small local procedures, and you can prevent one procedure from affecting all of the others. This makes it easier to code
large programs (there's less to remember as you code) and easier to track down bugs. The larger the program, the more useful this becomes.
In addition, if you code reusable procedures in SQCs, it's best to keep them local as much as possible, so that the programmers who
use those procedures don't need to check to see if they used the same variable names.
So, there are good reasons for using local procedures and variables.
Unfortunately, those little underscores and colons can give rise to their own bugs. On balance, I think that using local procedures
and variables is a very good idea, but it requires a little more awareness while you code.
|