Changing Value of Macro Variable inside SAS macro

2.9k views Asked by At

I am defining a macro variable inside a macro. Then, I am feeding it into a second macro. Inside macro2 counter changes value to 200. However, when I check what is inside the macro variable that I put in after macro 2 runs, it still says 0. I would like it to store the value 200? is this possible?

 %macro macro1();
   %let variable1= 0;
   macro2(counter=&variable1)

   %put &variable1;
 %mend macro1;

 %macro1;
2

There are 2 answers

2
sparc_spread On BEST ANSWER

You have a couple of issues here. First of all, you are missing the % before your call to macro2, but I suspect that's just a typo. The main issue is that you are trying to do what is referred to in other languages as call-by-reference. You can do this in SAS macro by passing the name of your variable rather than the value of your variable, and then use some funky & syntax to set the variable of that name to a new value.

Here is some sample code that does this:

%macro macro2(counter_name);
    /* The following code translates to:
    "Let the variable whose name is stored in counter_name equal
    the value of the variable whose name is stored in counter_name
    plus 1." */

    %LET &counter_name = %EVAL (&&&counter_name + 1);

%mend;

%macro macro1();
   %let variable1= 0;

   /* Try it once - see a 1 */
   /* Notice how we're passing 'variable1', not '&variable1' */
   %macro2(counter_name = variable1)
   %put &variable1;

   /* Try it twice - see a 2 */
   /* Notice how we're passing 'variable1', not '&variable1' */
   %macro2(counter_name = variable1)
   %put &variable1;
 %mend macro1;

 %macro1;

I actually have another post on StackOverflow that has an explanation of the &&& syntax; you can have a look at it here. Note that the %EVAL call has nothing to do with call-by-reference, it is just there to do the addition.

7
Joe On

Sparc_Spread explains how to "call by reference" in the SAS macro language, which may solve your problem.

In this particular case though, it's not necessarily crucial to use call by reference, and I'd argue it's not idiomatic to SAS macro language to use it (though certainly nothing wrong with it - it just looks a bit odd, and is a bit harder since it's not really a native concept, though certainly intentionally supported to be used that way if desired). There are two ways to get around this that both are very easy to use.

First of all, let's say you know the variable name you want to increment, and the starting value is the only interesting thing. Thanks to how SAS macro language handles scoping, with something not exactly lexical scoping and not exactly functional, it automatically will use the variable that already exists in the most local scope, when it does already exist (with some minor caveats, such as macros using DOSUBL).

So this works as expected:

%macro macro2(counter=);  
  %do variable1 =&counter. %to 200;
    %if %sysfunc(mod(&variable1.,50))=0 %then %put &=variable1;
  %end;

%mend macro2;

%macro macro1();
   %let variable1= 0;
   %macro2(counter=&variable1.);

   %put &=variable1;
 %mend macro1;

 %macro1;

(Of course, that is if you expect &variable1 to have the value of 201 - because %do loops, like do loops, always get incremented one higher than their ending value. I assume your real procedure works differently.)

That's because the &variable1. referred to in %macro2 automatically is the one present in the most local scope - which in this case is the scope of %macro1.


Alternatively, if you're using this %macro2 for the purpose of incrementing a counter, I would use a function-style macro method.

A function-style macro by definition is one that returns only a single value - and by returns I mean has a single value at the end of the macro's code that is presented in plain text (since a macro is, after all, only intended to create text that will then be parsed by the normal SAS language parser).

This can then be used on the right side of an equal sign in an assignment statement. The key is that it uses only macro language elements - %do loops and such - and no data step, proc, etc., language that would prevent it from being on the right side of an equal sign in an assignment statement (ie, x=%macrostuff(); cannot be x=proc sql(select...)).

So the following accomplishes the goal: increment a counter some amount, return the value (201, in this case, just like before), and then that can be assigned to a macro variable.

%macro macro2(counter=);  
  %do internal_counter =&counter. %to 200;
    %if %sysfunc(mod(&internal_counter.,50))=0 %then %put &=internal_counter.;
  %end;
  &internal_counter.
%mend macro2;

%macro macro1();
   %let variable1= %macro2(counter=0);

   %put &=variable1;
 %mend macro1;

 %macro1;

I would suggest that this is the most idiomatic way to accomplish this, and the most simple: you pass the value you want as input, function operates on it, returns value, which you then assign to a variable in your macro however you want.