I'd like some advice on sharing macro variables between macros. I've read a couple of papers describing best practice in writing macros, and the most pertinent information to me is the discussion on declaring variables as local within a macro in order to avoid any confusion in scope. I'm looking more for information on how best to "share" macro variables between macros. I know this can be done by using the global keyword at the head of the calling program and ensuring there is adequate commenting between macros. I'm coming from an Excel VBA programming background where variables can be passed between subroutines by including them as parameters in the function call. To my mind (and probably because I'm used to it), this seems clear. When I look at VBA function and subroutine code, I can immediately see the variables that are shared between functions/subroutines (variables can also be declared as global, but let's not get into that right now) I don't see this same clarity in SAS macro programming (probably because I'm not used to it!) How can I best inform anyone looking at my code how variables are shared between macros?
In this case were you want to derived the name of the output from the name in the input you can create what I call a "call routine" style macro. It alters the value of existing macro variable(s). In my example you declare the variable local before calling the macro. Works very much like data step call routines.
If you define a macro-variable as %global you can use it in different macros (?)
Parameters are passed this way:
%Macro xxx(Var1=);
.. code ..
%Mend;
%xxx(Var1=123);
Well, there are several factors in this. Let me see if I can elaborate, Global macro variables are for information which can be used anywhere in the session. For instance setting username could be a global variable. The thing to remember about global variables is that they stick around for the whole session, so you need to be careful that you are using one that has correct data for the given situation, for instance if I set a global macro up as TODAYS_DATE=15JUL2014, and then I run my program tomorrow its going to be wrong.
Local macro variables are useful for keeping information that is used within the scope of the block of code, e.g. within a macro. This then means that once a macro is completed these temporary variables are automatically removed, meaning they cannot be misused outside the block.
There is however a third option, which also has pluses and negatives. That is keeping parameters within a dataset. Say you have a macro which loops over 60 different where clauses printing out a page for each one, you could put 60 macro definitions in the macro statement, or you could have a dataset of where clauses and pass that dataset in. Keeps it nice and simple. The downside is its a bit more complicated to work with this over the normal macro variables.
User24feb provides an example above of named parameter passing:
%macro xyz (Var1=,Var3=,Var2=,Var7=Something);
In this instance it does not matter in which order the variables are used in the macro invocation, as they are named. You can also provide pre-defined values for option parameters, per Var7. The above style - in my opinion - is useful in situations where a macro is placed in a library and used by many people as it fixes input parameters so people don't need to know an order, can provide default data etc. Its easier to document.
You also have positional parameters:
%macro xyz (Var1,Var3,Var2,Var7);
This is less typing and if the order of parameters is know simple syntax. You do however lose the clarity, for instance you need to know to pass the data for Var3 at the second position rather than the third or you will end up with the wrong result. In my opinion this should only be used within the scope of other macros/blocks of code. The reason is clarity. Yes my macro:
%macro xyz (Dset=,Data_Order=,Output_location=);
Is clear and can be documented easily, user can understand that. This can have several sub macros:
%macro xyz (Dset=,Data_Order=,Output_location=);
%local some_var=abcd;
%macro Do_Loop (DS,LOC);
The end user does not need to know what do_loop is, how it operates, what positional parameters are, or if it will remain once they leave xyz. Hence that macro and macro variable are encapsulated.
This gets more important when talking about global standard libraries with many programs and programmers, so things like coding standards, documentation, examples, testing all become very important. Too often I see hundreds of sas files contain macros with no documentation examples, macro definitions with no relevance, useless macros (for instance why write a mysort() macro rather than use proc sort, it may save you a character or two to type, but if your not the initial programmer then it is not transparent - look up obfusication), no consistency, and macros which just don't work outside the environment they were initially programmed in. Macros, like Excel, get badly abused
There is one other option also, this is code generation. Basically your macro pre-processor function is a find and replace tool. It takes your code and expands it out to a full program by following logic in macros and macro variables. You can do this by hand with a useful function called
call execute(string);
With this you can generate a string which executes after the completion of the current step (except for macro calls). Thus you can create any amount of code in a datastep and then have that execute afterwards, e.g.
data _null_;
set my_really_big_list_of_datasets;
call execute('proc print data='||strip(dataset)||'; run;');
/* The above line is executed once per line in the dataset with the dataset name */
run;
This would generate the code:
proc print data=dataset_a; run;
proc print data=dataset_b; run;
proc print data=dataset_c; run;
...
So as you can see there are many different methodologies, styles and theories, but from my side the most important is to think of the next user of your code and how they will need to modify/use it.
Rw9, that is very nice try for elaboration. I would start that SAS-macros have the intention of changing the effective SAS-code. That is concept is not very common to languages.
The SAS environment (except DS2 language) is not set up as a strict programming language Strict programming language - Wikipedia, the free encyclopedia the definition of variables is done Lazy evaluation - Wikipedia, the free encyclopedia.
Unless you are setting global standards for a whole organization (eg data-time interval standards mnaming/location standards) making variables global can cause some confusion.
A common guideline is to keep code small (not more as 1 page) to keep it understandable. Splitting up a macro in several can help and you are getting nested calls SAS(R) 9.4 Macro Language: Reference (nested scope) the submacro can access the local variables of his caller. This nesting is isolating the scoping issue but is also pointing to a possible pitfall. Reusing of the same variable (the famous i) in counters can disturb the wrong one.
Do you really need macro processing or can you solve it with more common functions. The call execute, %include , dosubl, fcmp formats (many more) are offering very nice features not always known to programmers coming from an other language.
Hi,
You've already had a couple thoughtful responses. But I"m a bit confused by your question.
If what you are looking for is a way to pass macro variables between macros by including them as parameters in the macro call, this is definitely a useful feature.
So if you have:
%macro one(x=);
%put inside ONE, the local macro variable x=&x;
%two(x=&x) /*call %two, passing it the value of &x from %one*/
%mend one;
%macro two(x=);
%put inside TWO, the local macro variable x=&x;
%mend two;
%one(x=9)
In that setting, you will have a macro variable &x that is local to one, a macro variable &x that is local to two, and the value 9 will be passed from %one to %two.
HTH,
--Q.
Thanks, everyone, for taking the time to answer. I'll be more precise. Is it possible to return a value from a macro via one of its parameters? To take a very trivial example, and for purposes of illustration only, Let's say the following macro converts Sterling to Euro. %CallMacro(Var1=sterling,Var2=exch_rate, Var3=convertedamount) Is it possible to have a calculated value be returned via the third parameter? (I know that there are much easier ways to convert currency – this is to illustrate only!)
I think you would be better served then with either creating a compiled function, or an inline macro.
Compiled functions are under fcmp, and I wont go into that.
Inline macros though do help. The idea is to have the macro resolve as a piece of code within your datastep. For instance in you conversion:
%macro Convert (Value=,Conv_Rate=);
&Value * &Conv_Rate.
%mend;
data temp;
new_value=%Convert (Value=123, Conv_Rate=0.56);
run;
Here is an old favourite example from some time back, it basically resolves itself out to a number:
%macro nobs(ds);
%local nobs dsid rc;
%let dsid=%sysfunc(open(&ds));
%let nobs=%sysfunc(attrn(&dsid,NLOBSF));
%let rc=%sysfunc(close(&dsid));
&nobs
%mend nobs;
You can use it in:
data test;
set sashelp.cars;
number_of_obs=%nobs(sashelp.cars);
run;
Best practice for returning a value FROM a macro via one of its parameters is to pass the NAME of the macro variable that you want the macro to use to return the value as the value of a parameter.
For example I have a macro that will look up the number of observations in a SAS dataset. It has an optional parameter named MVAR which is used to tell the macro which macro variable to store the number into. If the named macro variable does not exist then it is defined as GLOBAL so that the caller will be able to see it.
Here is a dummy version of the macro that just returns 100.
%macro nobs
/*----------------------------------------------------------------------
Return the number of observations in a dataset reference
----------------------------------------------------------------------*/
(data /* Dataset specification (where clauses are supported) */
,mvar=nobs /* Macro variable to store result. */
);
%if not %symexist(&mvar) %then %global &mvar ;
%let &mvar=100;
%mend nobs;
%symdel nobs;
%nobs(sashelp.class);
%put nobs=&nobs;
Here is a fairly simple example of how to re-assign a value to the global variable defined outside the macro. As you can see VAR3 has been changed to the new value. So VAR3 can be viewed as the "return" value.
WRT documenting macro variables, my experience has been that they are not documented very well or simply not at all. I am guilty of that as well. That's why I usually run code with:
OPTIONS SYMBOLGEN MACROGEN;
The example you provide later is probably some code that was written for one purpose and then modified later on and as such suffers from code bloat. Extraneous code is left in because it doesn't hurt anything.
It all comes down to style too. Some people like to indent their code with a few spaces and other use a tab. As you can see my style is "flatearther".
%let var1 = 13;
%let var2 = 6;
%let var3 = 0;
%MACRO callmacro(x1, x2, x3);
%let &x1 = %eval(&&&x1 - &&&x2);
%MEND;
%put &var3;
%callmacro(var1, var2, var3);
%put &var3;
That question of return values is going back to the concept of the macro processing.
The goal is modifying the SAS code as text, it has no programming goal.
What is that meaning..... think about that .....
Every open text is your returning value. Tt is even not only one but a lot of possible text values and text lines.
There is something weird about that.
- Calling a macro like %xxx should by itself not being closed by the famous " ; "
- The generated text can be all type of inline code not needing the infamous " ; "
Well everybody is telling you you should code these ; and now someone not needing them.
That is a result of details it is changing your code text processing. See the concepts SAS(R) 9.4 Macro Language: Reference
For example SAS(R) 9.4 Macro Language: Reference the examples are not needing the ; they are not typo-s.
Why you are still needing that ; ? sometimes SAS need to know the end of the statements a boundary in the coding.
When there is no ; it could be seen as there can come more code ... check/wait for that (never coming)
I'll be more explicit. Here's the macro: %macro GetData(mpfile); **The macro variable InputData will be created in the GetData macro, ; %global InputData; filename TempFile temp lrecl=4000; data _null_; infile "&mpfile" lrecl=4000; file TempFile; input; if lengthc(_infile_) > 0 then do; if substr(_infile_,1,1) in ("$","!","*") then put _infile_; end; run; data _null_; slashloc = length("&mpfile") - index(reverse(trim("&mpfile")),'\') + 1; **We subtract 4 at the end to remove the file extension; InputData = substr("&mpfile",slashloc+1,length("&mpfile")-slashloc-4); call symputx('InputData',InputData); run; **proc import is cancelled if there are no observations in TempFile. No output dataset is produced. ; **Will need to test later if that output dataset exists or not before doing any processing on it; proc import datafile = TempFile out = &InputData dbms = csv replace; getnames=yes; guessingrows=5000; run; %mend; ************* In the middle data step, a macro variable is created, &InputData, which is declared as Global at the top of the macro. It isn't obvious in the program that calls the macro that this global variable is created here. An alternative would be to declare it as global in the calling program, but then someone might ask, "why is he declaring a variable that he isn't using? Where is he ising it?" Both program and macro work correctly as they are, my question is, is there a *clearer* way of doing this? Clearer to any future programmers?
IMO defining global macro variables in a macro is not a good idea. It will probably be ok if your one person in a local install of SAS where your unlikely to use it much or overwrite, assume a certain value. Why not just add a macro variable in your call to define what you want the output dataset to be called, then there is no need for global variables as the program calling the macro knows exactly what the output will be created as:
%macro GetData(mpfile=,Output_dataset=);
filename TempFile temp lrecl=4000;
data _null_;
infile "&mpfile" lrecl=4000;
file TempFile;
input; if lengthc(_infile_) > 0 then do;
if substr(_infile_,1,1) in ("$","!","*") then put _infile_;
end;
run;
proc import datafile = TempFile out = &Output_dataset. dbms = csv replace;
getnames=yes;
guessingrows=5000;
run;
%mend;
In this case were you want to derived the name of the output from the name in the input you can create what I call a "call routine" style macro. It alters the value of existing macro variable(s). In my example you declare the variable local before calling the macro. Works very much like data step call routines.
To extract a file name from a path this might be good enough.
16 %let path=/path/to/my/csvfile.csv;
17 %let name=%scan(%superq(path),-2,%str(./));
18 %put NOTE: &=name &=path;
NOTE: NAME=csvfile PATH=/path/to/my/csvfile.csv
Of course there is no guarantee that the FILENAME will be a valid SASNAME so you will need to hope for the best or verify that it is a valid SASNAME.
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.