In the third quarter of 2016, a new appendix entitled "Macro Examples" was added to the SAS 9.4 Macro Laguage: Reference document. There are 17 programs with descriptions for you to use. You can view the new appendix online at support.sas.com/documentation.
Here's a list of what you'll find:
I just opened Example 15: Delete a File on UNIX or Windows If It Exists
It could be much improved by using function fdelete() rather than a system command.
This is better because it:
- demonstrates another SAS function
- avoids flashing a terminal window in SAS desktop
- works even if the user has no access to system commands
- simplifies the macro to make it OS-agnostic.
Two more comments:
- calling a deletion macro %check() is not good practice
- calling fileexist() again to confirm deletion would show good practice
Ok, I opened another example: Example 14: Create a Quoted List Separated by Spaces
Surely, while what is shown is interesting, the standard method for doing this should be demonstrated as well:
proc sql noprint;
select quote(strip(EMPL)) into :list separated by ' ' from ONE;
quit;
Hey ChrisNZ,
Thanks for the two comments you entered. I've sent your comments to Tech Support.
Thank you for not taking the comments negatively Sue. I was dreading this. 🙂
Generally speaking, I reckon that SAS-supplied code should be exemplar.
It doesn't take much. Consider the code for example 11. Surely this
%macro date_loop(start, end, period);
%local dif i date;
%let start = %sysfunc(inputn(&start ,anydtdte9. ));
%let end = %sysfunc(inputn(&end ,anydtdte9. ));
%let dif = %sysfunc(intck (&period,&start,&end));
%do i=0 %to &dif;
%let date=%sysfunc(intnx(&period,&start,&i,b),date9.);
%put &date;
%end;
%mend date_loop;
%date_loop(01jul2015, 01feb2016, month)
is better than
%macro date_loop(start,end);
%let start=%sysfunc(inputn(&start,anydtdte9.));
%let end=%sysfunc(inputn(&end,anydtdte9.));
%let dif=%sysfunc(intck(month,&start,&end));
%do i=0 %to &dif;
%let date=%sysfunc(intnx(month,&start,&i,b),date9.);
%put &date;
%end;
%mend date_loop;
%date_loop(01jul2015,01feb2016)
ChrisNZ,
Please keep the comments coming. I forward them to Tech Support for review. They are going to use your advice for Example 15. We are changing that one during our next release.
Thanks,
Sue
Whell, @ChrisNZ you've inspired me. I glanced at these, rolled my eyes at a few, and looked away. It's a hard task to write a consistent set of macros, and often the examples I've seen in support notes are not really exemplary (in my eyes), which is understandable as they've been written by a variety of authors over a wide range of time. But to add a new section of macros to the documentation, I think the quality bar should be higher.
Example 10, Retrieve Each Word from a Macro Variable List
%let varlist = Age Height Name Sex Weight; %macro rename; %let word_cnt=%sysfunc(countw(&varlist)); %do i = 1 %to &word_cnt; %let temp=%qscan(%bquote(&varlist),&i); &temp = _&temp %end; %mend rename; data new; set sashelp.class(rename=(%unquote(%rename))); run;
The %Rename macro has no parameters defined, the so the parameter is passed as a global macro variable. Macro vars created in the macro are not declared local. The macro will choke if given an empty list. These are basic concepts that are covered into any introductory macro course.
And perhaps this is a style choice, but I wouldn't worry about %quoting the var names and %unquoting them during the macro incvocation. Because I don't use special characters in var names. So I would use something like:
%macro rename(varlist=); %local i var; %do i = 1 %to %sysfunc(countw(&varlist,%str( ))); %let var=%scan(&varlist,&i,%str( )); &var = _&var %end; %mend rename; data new; set sashelp.class(rename=( %rename(varlist=Age Height Name Sex Weight) )); run;
I suppose if your really want the quoting could use:
%macro rename(varlist=); %local i var; %do i = 1 %to %sysfunc(countw(%bquote(&varlist),%str( ))); %let var=%qscan(%bquote(&varlist),&i,%str( )); %unquote(&var) = _%unquote(&var) %end; %mend rename; data new; set sashelp.class(rename=( %rename(varlist=Age Height Name Sex Weight) )); run;
That would at least hide the unquoting from the user.
Of course as with any macro, you could add more parameters (for the delimiter, etc), and add functionality. I'm fine with the documentation including "basic" macros. But it's important that even in such "simple" macros, best practices are illustrated and explained.
Hey ChrisNZ,
Example 15 has been updated and will be in the next SAS release. Thanks again for the comments.
Sue
Example 2: List All Files within a Directory Including Subdirectories
It does not really list all files within a Directory including sub-directories... It only list the files that has the same Extension as the 2nd parameter being passed to the macro.
In addition, it determines a file by checking if the file name has a dot, if it does not have a dot, then it's considered as a directory, but a file's name does not have to contain a dot, it may be a better option to use dopen() to determine a directory by checking if an Directory ID (did) is returned, as it's already using this to determine if the parent level directory can be opened before getting the childen.
Ray
@RZ1 Most of these examples need some rework. The deletion macro #15 is still called %check. Sigh..
Do you want feedback on them? What type of feedback?
I would assume bug reports are welcome, but doubt we should try to turn these simple examples into "idiot proof" functions.
How were these examples chosen? Are they just examples that already existed that were consolidated into a new appendix? Iis there an attempt in these example to cover specific macro functionality or methods? Is there a master list of the types of functionality that you want to cover and which examples cover what functionality? Or perhaps they were based on frequency of questions on SAS Communities or to SAS Support? Are there functionalities that are missing examples now?
Do you want feedback on Coding Style issues? Would you prefer to use a consistent style? Or use different styles in different examples to show that there is not just one style of coding that you can use? Some coding style issues can probably be applied almost universally (like not having random indenting such as in code in Example 1) but other's are not (like @ChrisNZ use of multiple spaces before the equal signs in his %LET statements in his suggestion for Example 11). My biggest pet peeve is I hate when people leave block termination lines (END, %END, RUN, QUIT, etc) indented instead of aligning them back with the same indentation level of the beginning line of the block. It always gives me the sensation of having miscounted the number of steps in a flight of stairs. But many people seem to like that style.
@Tom I like aligning as many things as possible, it saves (me) time because anything missing becomes obvious. But I do understand that other may see differently.
They'd be wrong of course... ;o)
Let's take a look at Example 12: Using Macro Variables within a CARDS or DATALINES Statement
Obvious issues
- The program code is not consistently indented. Why does the %LET statement appear to be two spaces to the left of the data step? The assignment statement is indented, but the input statement is not. Why do the copies of the lines in the program description section have different indentations than the lines in the program section?
- The name of the macro variable is DOG, not &DOG. &DOG is how to reference the VALUE of the macro variable named DOG.
- The description is useful in explaining how to interpret the nested function calls in the assignment statement. But it does not explain WHY the nested function calls were used. Why were the QUOTE() and DEQUOTE() function calls added? It also does not really explain what the RESOLVE() function does. Not sure how you want to word it. Perhaps something along the lines of "Quoting the value will make the macro processor see the value passed to it be RESOLVE() as a string literal and not a potential macro statement".
- Not sure that I would say that you use a DATALINE section "to create the data". Perhaps something more like place the lines of text between the DATALINES statement and the line with a semicolon.
Enhancement opportunity
- Should explain WHY you might need this. An example what happens when you read the datalines without also calling RESOLVE() function might help with that.
- Should there be a section to explain issues that users might have using this code? For example if you create a character variable of length 40 and then expand some of the macro variable references or macro calls in the string you could end up with a result that will not fit back into the original 40 characters.
- Should refer to PROC STREAM for another way to convert macro triggers in source text.
Example 14: Create a Quoted List Separated by Spaces
Obvious issues
- Again with the inconsistent indentation and differences in indentation between program and program description.
- Why use STRIP() function instead of TRIM() function? That will make it impossible to use the generated macro variable to match the data from the original file if any of the values had leading spaces.
- Why does the %LET statement that is passed to CALL EXECUTE() not have an ending semi-colon? And more surprising why does it work without it?
- Explain why it is important to use single quotes around the string in the CALL EXECUTE() statement instead of double quotes.
Enhancement opportunity
- Should explain WHY you would want to do this instead of just use CATX() function in a data step. Hint: macro variables can be longer than character variables.
- Why does it use CALL EXECUTE() instead of RESOLVE() like in example 12? Is it again related to limits of data step character variables versus macro variables?
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning and boost your career prospects.