In my previous blog post, I mentioned that I love the power of the SAS macro language and the power of my yellow Corvette. Now, I want to tell you how to avoid a major pitfall along the “macro programming road.”
At the risk of dating myself, you may recall the Jan and Dean tune, “Dead Man’s Curve,” and its lyrics that warned:
“Dead Man's Curve, it's no place to play
Dead Man's Curve, you best keep away
Dead Man's Curve, I can hear 'em say
Won't come back from Dead Man's Curve.”
While Dead Man’s Curve can put your wheels in a ditch, macros with unvalidated parameters can put your SAS code in a ditch.
For example, the unvalidated dataset names in the DATASETS parameter below generate invalid PROC MEANS code with errors in the SAS log:
%macro stats(datasets); %let i=1; %let dsn=%upcase(%scan(&datasets,1)); %do %while(&dsn ne ); title "ORION.&dsn"; proc means data=orion.&dsn n min mean max; run; %let i=%eval(&i+1); %let dsn=%upcase(%scan(&datasets,&i)); %end; title; %mend stats; %stats(dead mans curve)
To keep your SAS code out of this ditch, validate the DATASETS parameter with the EXIST function, as shown below:
%macro stats(datasets); %let i=1; %do %until(&dsn= ); %let dsn=%upcase(%scan(&datasets,&i)); %if &dsn= %then %put NOTE: Processing complete.; %else %if %sysfunc(exist(orion.&dsn)) %then %do; title "ORION.&dsn"; proc means data=orion.&dsn n min mean max; run; %end; %else %put ERROR: No &dsn dataset in ORION library.; %let i=%eval(&i+1); %end; %mend stats; %stats(dead mans curve)
Let's take this concept one step further. Suppose you add a VAR statement to the PROC MEANS code and a corresponding VAR parameter to the macro. Then, what should you do? Not only do you need to (1) validate that the dataset exists, you also need to (2) validate that all variables exist within the dataset, and (3) validate that all variables are numeric.
You can learn more about this topic and other advanced parameter validation techniques in our new SAS Training course, SAS Macro Language 2: Advanced Techniques.
Meanwhile, drive safely, validate your macro parameters, and remember: "Dead man's curve, you best keep away."
6 Comments
Good point, Jim. The part where I have struggled in the past is figuring out what to do with the invalid arguments. Certainly putting an ERROR: message in the log is a good first step but the program will still continue to execute any following steps. I've often coded dummy data steps to utilize an ABORT statement to effectively stop the program. Any other approaches that you or your readers might suggest?
Thanks, Clint for the timely question. The method which I illustrate in SAS Macro Language 2: Advanced Techniques is to issue a %RETURN statement (usually based on a %IF condition) to stop macro execution and prevent the generation of invalid sascode.
Good advice, and I'll carry it a step further. When I write macro definitions that take multiple arguments, I validate all of them even if I find an early error. This is simple enough, just define a local error flag macro var, initialize it to zero, and set it to one if you detect an error. Then generate/execute code only if the error flag is off.
The reason for this is to help users of the macro to debug their code faster. I find it annoying to fix one typo only to find that I've made another that the first test didn't reveal. With this technique, all bugs can be detected in a single test run.
This may be a little overkill for a macro with only a few arguments, but some of mine have many args.
Excellent suggestion, Alan.
LeRoy, thank you for your suggestions. The code is now copyable.
In practice, programmers would likely use neither ORION nor SASHELP, but the name of their own allocated library. For greater flexibility, I would suggest a LIBRARY= parameter with a default value of WORK.
Though the concepts in this particular example are not new to me, I am curious why (maybe it is a problem of my web browser) I am unable to copy the code portions out of the example. The text parts are copyable, but not the code.
Also, I think that the example could more usefully have been one that (once you have copied out the code, or transcribed it, into your SAS Program Editor window) could be run by ANY SAS user, not just SAS users with the Orion Data Library.
The SASHELP data library is available wherever SAS is installed, regardless of version. And what's at issue here is the SAS data set, not the SAS data library. Running the code with an unavailable data library reference will make it fail differently.
I love the power of SAS Macro Language and my red SLK 350.