This week’s author tip is from Robert Virgile and his book SAS Macro Language Magic: Discovering Advanced Techniques. Virgile chose this tip because even good programmer’s make errors.
We hope you find this tip useful. You can also read an excerpt from Virgile’s book.
Even good programmers make errors. In fact, when it comes to macro language, good programmers often make the same error: failing to define macro variables as %local.
Here is an all-too-common example:
%macro doses; %do i=1 %to 3; dose&i date&i %end; %mend doses;
This straightforward macro generates a list of variable names. For example, PROC PRINT could call it:
proc print data=patients; var patient %doses; run;
The intention is to complete the VAR statement:
proc print data=patients; var patient dose1 date1 dose2 date2 dose3 date3; run;
The macro works, and the result is correct. So where is the error? Why should this macro add just before the %DO loop:
Danger abounds! But to understand why, you need to understand when and where the software creates &i. To begin, consider this test:
%let i=5; %macro test; %local i; %let i=2; %mend test; %test %put i is now &i..;
When %LET appears outside of a macro definition, it creates &i in the global symbol table. When %TEST executes, the %LOCAL statement creates &i in the local symbol table for %TEST. So there are two macro variables, both named &i.
When the interior %LET statement executes, which &i receives a value of 2? The interior %LET statement triggers a search for a macro variable named &i, and uses the first one it finds. The search process begins in the local symbol table, which does in fact contain &i. So the local &i receives a value of 2.
When the macro finishes executing, its local symbol table vanishes. The %PUT statement writes:
i is now 5.
The only remaining macro variable named &i is the one in the global symbol table, which still has a value of 5.
Next, consider a similar example that removes the %LOCAL statement:
%let i=5; %macro test; %let i=2; %mend test; %test %put i is now &i..;
The process doesn’t change, but the result does. The %PUT statement writes:
i is now 2.
The key step that reverses the outcome is the interior %LET statement. As before, it triggers a search for an existing macro variable named &i. It searches unsuccessfully in the local symbol table, so it continues the search in the global symbol table. Finding &i there, that &i receives a value of 2.
While the scenarios can become more complex (when %A calls %B, and %B calls %C, and %C calls %D), the search process remains the same. When a program references a macro variable, the software searches for an existing variable with that name. Search from the most interior symbol table outward, ending up with the global symbol table if necessary. The software uses the first macro variable found that has the proper name. If none of the symbol tables contains the macro variable, create it. Where? That is another story for another day. The search process tells us all we need to know to understand why the %LOCAL statement is important.
The missing %LOCAL statement causes trouble when you write a macro that calls %DOSES. Here is a simple example:
%macro your_macro; %local i; %do i=1 %to 3; proc print data=patient_pool&i; var patient %doses; end; %end; %mend your_macro;
The intent is to print three data sets. The result is quite different, even though you were quite careful to add your own %LOCAL statement.
When %YOUR_MACRO executes, the %LOCAL statement creates &i in its local symbol table. When %DOSES looks for an existing macro variable named &i, it follows the standard procedure:
- Search the local %DOSES symbol table (unsuccessfully, because %DOSES is missing the %LOCAL statement).
- Search the local %YOUR_MACRO symbol table (successfully).
- Use &i in the local %YOUR_MACRO symbol table.
Since %DOSES increments &i in %YOUR_MACRO’s symbol table, %YOUR_MACRO prints only one data set. After that, &i exceeds the upper %DO loop limit of 3, and %YOUR_MACRO finishes. No error messages or warning messages appear … but only one data set prints.
With a slight change, the problem gets worse:
%macro your_macro; %local i; %do i=1 %to 8; proc print data=patient_pool&i; var patient %doses; end; %end; %mend your_macro;
The same search process applies, with %DOSES using &i from the %YOUR_MACRO symbol table. This means that the program is an infinite loop! Each time %DOSES executes, it resets &i so that it never reaches a value of 8.
The bottom line: define your local variables using a %LOCAL statement. If you don’t, you can suffer wrong results, infinite loops, and a reputation for writing macros that are dangerous.
For more information about the macro language and the magic you can create with it, check out Robert Virgile’s book SAS Macro Language Magic: Discovering Advanced Techniques.