***************************************************************************************************************l %let pgm=better_macro; /* The purpose of this post is to discuss 'better' macro interfaces */ /* SAS does not make it easy to scope variables. In paticular it does not */ /* allow us to send results back to the calling macro unless we make the results global*/ /* creating global macro variables is usually not a good idea (except for functions) */ /* we would like to do the following */ /* but not have Area and Volume macro variables global */ /* Code below passes all the special cases 'proc sql' 'go to' 'sysbuff' at invocation */ /* THIS IS NOT EASY */ %macro GetAreaVolume(figure=Square); %AreaVolume(figure=square,length=3,Area=,Volume=); %put Area=&Area Volume=Volume; /*we would like Area=9 Volume=27 */ %mend GetAreaVolume; %GetAreaVolume; %put Area=&Area Volume=Volume; /* Area and Volume are not global */ Area=&Area Volume=&Volume /* HERE IS THE STANDARD SOLUTION (Same as the 10th solution below) */ /* This requires that the parent know all the variables coming back from the child */ /* does not allow area and volume to be part of the argument list which is nice documentation for the child */ %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Figure=square ,Length=3 ); %let area = %sysevalf(&length**2); %let volume= %sysevalf(&length**3); %mend AreaVolume; %macro GetAreaVolume(Type=GeometicFigures); %local area volume; %AreaVolume(Figure=square,Length=3); /* this semicolon is required */ %put &Area &Volume; /* area and volume are returned data */ %mend GetAreaVolume; %GetAreaVolume; %put &Area &Volume; /* HERE IS THE 1st SOLUTION */ /* THIS WORKS EVEN IF %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Figure=square ,Length=3 /* the local macro variables BELOW are returned to the calling proram */ /* these macro variables will be placed in the scope of the calling program */ /* supplying values for these macro variables on invocation will have no effect */ /* the argument list is the contract between the calling and called program */ ,Area=Area_Square ,Volume=Volume_Square ); /* I put some dummy code her */ proc sql;select * from sashelp.class;quit; data x;set sashelp.class;run; %goto foo; %foo : %let x = this is a test; /* do not make any changes ater this comment */ Data _null_; Area =put(&length**2,4. -l); Volume=put(&length**3,4. -l); call symputx('Area',Area); call symputx('Volume',Volume); run /* do not put a semicolon after run */ %mend AreaVolume; %macro GetAreaVolume(Type=GeometicFigures); %AreaVolume(Figure=square,Length=3,Area=,Volume=); /* this semicolon is required */ %put &Area &Volume; /* area and volume are returned data */ %mend GetAreaVolume; %GetAreaVolume; %put &Area &Volume; /* HERE IS THE 2nd SOLUTION */ /* It is a slight variation on 6th solution below (Richard A. DeVenezia) */ /* This uses an agreed upon save area in the calling program */ /* This save area should be identical for all calling macros */ /* %local Utl_ReturnCount Utl_ReturnNames Utl_ReturnValues */ /* Utl_ReturnCount = Number of macro variables returned from called macro. */ /* Utl_ReturnNames = Names of macro variables returned */ /* Utl_ReturnValues = Values assigned to macro variables returned */ %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Figure=square ,Length=3 /* do not specify these values on call little c means contents */ /* this shows the contract between called and calling macro and is valuable information */ ,ReturnCount =c(2) /* number of macro variables returned */ ,ReturnNames =c(Area Volume) /* name of macro variables returned */ ,ReturnValues=c('%let area=9;%let volume=27;') /* values for macro variables */ ); %let &ReturnCount=2; %let &ReturnNames=Area Volume; Data _null_; Area = cats('%let Area =',put(&length**2,4.),';'); Volume = cats('%let Volume=',put(&length**3,4.),';'); Call symputx("&ReturnValues",cats("'",Area,Volume,"'")); run; %mend AreaVolume; %macro GetAreaVolume(Figure=square); /* This is the save area. That will be populated from the called program */ /* All parent macros that expect return information */ /* from the called macro must have this exact local statement */ %local Utl_ReturnCount Utl_ReturnNames Utl_ReturnValues; %AreaVolume( Figure=square ,Length=3 ,ReturnCount =Utl_ReturnCount /* number of macro variables returned */ ,ReturnNames =Utl_ReturnNames /* name of macro variables returned */ ,ReturnValues=Utl_ReturnValues) /* values for macro variables */ %put &Utl_ReturnValues; %sysfunc(dequote(&Utl_ReturnValues)) %put Utl_ReturnCount=&Utl_ReturnCount; %put Utl_ReturnNames=&Utl_ReturnNames; %put Area=&Area Volume=&Volume; %mend GetAreaVolume; %GetAreaVolume(Figure=square); %put Area=&Area Volume=&Volume; /* HERE IS THE 3rd SOLUTION */ /* IT USES THE SAS AUTOMATIC GLOBAL MACRO VARIABLE AFSTR1 */ %symdel length; %symdel area; %symdel volume; %macro quit/des="get return data from child macro - eliminates global macro variables"; sysfunc(dequote(&&afstr1)) = dummy %mend quit; %macro AreaVolume( Length=3 ,Area=Area_Square ,Volume=Volume_Square ); %let afstr1=; /* insert additional code her */ /* do not make any changes ater this comment */ Data _null_; Area = cats('%let Area =',put(&length**2,4.),';'); Volume = cats('%let Volume=',put(&length**3,4.),';'); Call symputx('afstr1',cats("'",Area,Volume,"'")); run; %mend AreaVolume; %macro GetAreaVolume(Figure=Square); %AreaVolume(Length=3,Area=,Volume=); %let noop=%eval(%%quit); %put &Area &Volume; %mend GetAreaVolume; %GetAreaVolume; /* the macro variables are not global !!!!! */ %put &Area &Volume; /* HERE IS THE 4th SOLUTION */ /* Declare the macro variables Area and Volume global using symputx */ /* unfortunately we have to remove the variables from the macro arguments */ /* this leaves the contract between macros less clear */ %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Length=3 ); /* insert additional code her */ Data _null_; Area =put(&length**2,4. -l); Volume=put(&length**3,4. -l); call symputx('Area',Area,'G'); call symputx('Volume',Volume,'G'); run; %mend AreaVolume; %macro GetAreaVolume(Figure=Square); %AreaVolume(Length=3); %put &Area &Volume; %mend GetAreaVolume; %GetAreaVolume; /* the macro variables are not global !!!!! */ %put &Area &Volume; /* HERE IS THE 5th SOLUTION */ /* same as second solution but does not require the quit macro */ /* replaces the quit macro with %sysfunc(dequote(&afstr1)); */ /* This method uses and intermediate macro variables and allows */ /* for arrays of macro variables to be retuned to the calling */ /* program */ %macro AreaVolume( Length=3 ,Area=Area_Square ,Volume=Volume_Square ); %let afstr1=; /* insert additional code her */ /* do not make any changes ater this comment */ Data _null_; Area = cats('%let Area =',put(&length**2,4.),';'); Volume = cats('%let Volume=',put(&length**3,4.),';'); put area= volume=; Call symputx('afstr1',cats("'",Area,Volume,"'")); run; %mend AreaVolume; %macro GetAreaVolume(Figure=square); %local Area Volume; %AreaVolume(Length=3,Area=,Volume=); %sysfunc(dequote(&afstr1)); %put &Area &Volume; %mend GetAreaVolume; %GetAreaVolume; /* the macro variables are not global !!!!! */ %put &Area &Volume; /* HERE IS THE 6th SOLUTION */ /* classic global macro approach */ /* Declare the macro variables Area and Volume global using %global */ /* unfortunately we have to remove the variables from the macro arguments */ %symdel length; %symdel area; %symdel volume; %global area volume; %macro AreaVolume( Length=3 ); /* insert additional code her */ Data _null_; Area =put(&length**2,4. -l); Volume=put(&length**3,4. -l); call symputx('Area',Area); call symputx('Volume',Volume); run; %mend AreaVolume; %macro GetAreaVolume(Figure=Square); %global Area Volume; %AreaVolume(Length=3); %put &Area &Volume; %mend GetAreaVolume; %GetAreaVolume; /* the macro variables are not global !!!!! */ %put &Area &Volume; /* HERE IS THE 7th SOLUTION */ /* Courtesy of Richard A. DeVenezia */ /* Note it is possible to use this method with pure macro code */ /* this method uses macro variables that contain macro variable names */ %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Figure=square ,Length=3 ,ReturnVars= ,ReturnArray= ); %let &ReturnVars=Area Volume; Data _null_; Area = cats('%let Area =',put(&length**2,4.),';'); Volume = cats('%let Volume=',put(&length**3,4.),';'); Call symputx("&ReturnArray",cats("'",Area,Volume,"'")); run; %mend AreaVolume; %macro GetAreaVolume; %local Names Values; %AreaVolume(Figure=square,Length=3,ReturnVars=Names,ReturnArray=Values) %sysfunc(dequote(&Values)) %put Names=&Names; %put Area=&Area Volume=&Volume; %mend GetAreaVolume; %GetAreaVolume; %put Area=&Area Volume=&Volume; /* HERE IS THE 9th SOLUTION */ /* Pure macro code solution */ /* Very similar to 6th method */ /* See earlier SAS post */ %macro inner(mvstatements=, mvlocals=); %put statements=&statements; %let &mvlocals = status_1 status_2 status_3; %let &mvstatements = %nrquote(&&&mvstatements)%nrstr(%let )status_1=good1%str(;); %let &mvstatements = %nrquote(&&&mvstatements)%nrstr(%let )status_2=bad1%str(;); %let &mvstatements = %nrquote(&&&mvstatements)%nrstr(%let )status_3=ugly1%str(;); %mend; %macro outer; %local locals statements; %inner (mvstatements=statements, mvlocals=locals) %put statements=&statements; %unquote(&statements) %put status_1=&status_1; %put status_2=&status_2; %put status_3=&status_3; %mend; %outer; %put status_1=&status_1; /* There is one drawback with passing macro variable names to inner macros, collisions. When the macro variable named in the parameter matches a macro variable local to the inner scope, the macro var you expect to have been set by the inner macro will remain unchanged upon return from the macro call. This is because the assignment was done to a macro var local to an inner scope that no longer exists. */ %macro inner(mvstatus=); %local status; %let status = FAILURE; %let &mvstatus = SUCCESS; %put inner: &mvstatus=&&&mvstatus; %mend; %macro outer; %local status; %let status=UNKNOWN; %inner (mvstatus=status) %put outer: status=&status (expecting SUCCESS); %mend; %outer /* outer: status=UNKNOWN (expecting SUCCESS) */ /* was surprised that the call symput method worked */ /* I not sure why */ %symdel status; %symdel mvstatus; %macro inner(mvstatus=); %local status; %let status=FAILURE; data _null_; call symputx("&mvstatus",'SUCCESS'); run %mend inner; %macro outer; %local status; %let status=UNKNOWN; %inner (mvstatus=status); %put outer: status=&status (expecting SUCCESS); %mend; %outer %put &status; /* HERE IS THE STANDARD SOLUTION */ /* This requires that the parent know all the variables coming back from the child */ /* does not allow area and volme to be part of the argument lis which is nice documentation for the child */ %symdel length; %symdel area; %symdel volume; %macro AreaVolume( Figure=square ,Length=3 ); %let area = %sysevalf(&length**2); %let volume= %sysevalf(&length**3); %mend AreaVolume; %macro GetAreaVolume(Type=GeometicFigures); %local area volume; %AreaVolume(Figure=square,Length=3); /* this semicolon is required */ %put &Area &Volume; /* area and volume are returned data */ %mend GetAreaVolume; %GetAreaVolume; %put &Area &Volume;