Discussion:
Macros that return values in specified macro variables
(too old to reply)
Richard A. DeVenezia
2003-11-24 14:30:46 UTC
Permalink
Sometimes a macro has to return a status value to the caller so the caller
can make a decision. Typically the status value is some sort of error code,
other times, the value is one of several that get set by the macro.

Consider this macro

%macro attrc (data=, lib=, mem=, rc=);
%local dsid;
%let dsid = %sysfunc (open (&data));
%if &dsid %then %do;
%let &lib = %sysfunc (attrc(&dsid,LIB));
%let &mem = %sysfunc (attrc(&dsid,MEM));
%let &rc = 0;
%end;
%else %do;
%let &rc = -1;
%end;
%mend;

options nosource;

%let libname=;
%let memname=;
%let rc=;
%let xrc=;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;


The invocations have the log showing
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
xrc=0
libname=SASHELP
memname=CLASS
xrc=-1


One first blush I would want to say
%if &rc eq 0 %then <ok to proceed with libname and memname>

So, why did the first two invocations show rc = blank ?
Because the macro variable specified to contain the return value is also the
name of a macro variable local in scope to the macro invoked.

Thus I posit:

What is a good strategy to prevent variable name collision in macros that
are supposed to set macro variables in scope above themselves ?

Worst case I would want the macro called to generate an ERROR message to the
log if it is request to place a value in a macro variable that will be local
when the macro is running.

Is there a programmatic way to determine which macro variables are currently
defined at local scope (without going to SASHELP.VMACRO)?

Perhaps something like

%if %isLocal ( &rc ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;
...
%let &rc = < some return code value >;

or

%if &rc %in ( &_LOCALS_ ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;

where macro variable _LOCALS_ is a reserved automatic macro variable listing
all the macro variables having local scope. (Probably not feasible;
consider a macro utilizing a macro 'array' having thousands of elements)
--
Richard A. DeVenezia
Groeneveld, Jim
2003-11-24 15:17:04 UTC
Permalink
Hi Richard,

You are right. How about testing each argument, as far as necessary, but an identically named value? E.g. within your macro:
%IF %UPCASE(&RC) EQ RC %THEN ......... abort macro for instance.

The problem is that your RC macro variable inside your macro by definition is local, while you would want to access the global (or local at the calling level) same-named macro variable, and that is impossible.

It is not sufficient to know which macro variables are defined at any (calling) level, because they have to exist at the calling level in order to receive a value (like XRC; if you would not have defined it empty initially, the construct would not have worked at all). You have to prevent identical naming.

Regards - Jim.
--
. . . . . . . . . . . . . . . .

Jim Groeneveld, MSc.
Biostatistician
Science Team
Vitatron B.V.
Meander 1051
6825 MJ Arnhem
Tel: +31/0 26 376 7365
Fax: +31/0 26 376 7305
***@Vitatron.com
www.vitatron.com

My computer has the solutions, I have the problems.

[common disclaimer]


-----Original Message-----
From: Richard A. DeVenezia [mailto:***@IX.NETCOM.COM]
Sent: Monday, November 24, 2003 15:31
To: SAS-***@LISTSERV.UGA.EDU
Subject: Macros that return values in specified macro variables


Sometimes a macro has to return a status value to the caller so the caller
can make a decision. Typically the status value is some sort of error code,
other times, the value is one of several that get set by the macro.

Consider this macro

%macro attrc (data=, lib=, mem=, rc=);
%local dsid;
%let dsid = %sysfunc (open (&data));
%if &dsid %then %do;
%let &lib = %sysfunc (attrc(&dsid,LIB));
%let &mem = %sysfunc (attrc(&dsid,MEM));
%let &rc = 0;
%end;
%else %do;
%let &rc = -1;
%end;
%mend;

options nosource;

%let libname=;
%let memname=;
%let rc=;
%let xrc=;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;


The invocations have the log showing
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
xrc=0
libname=SASHELP
memname=CLASS
xrc=-1


One first blush I would want to say
%if &rc eq 0 %then <ok to proceed with libname and memname>

So, why did the first two invocations show rc = blank ?
Because the macro variable specified to contain the return value is also the
name of a macro variable local in scope to the macro invoked.

Thus I posit:

What is a good strategy to prevent variable name collision in macros that
are supposed to set macro variables in scope above themselves ?

Worst case I would want the macro called to generate an ERROR message to the
log if it is request to place a value in a macro variable that will be local
when the macro is running.

Is there a programmatic way to determine which macro variables are currently
defined at local scope (without going to SASHELP.VMACRO)?

Perhaps something like

%if %isLocal ( &rc ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;
...
%let &rc = < some return code value >;

or

%if &rc %in ( &_LOCALS_ ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;

where macro variable _LOCALS_ is a reserved automatic macro variable listing
all the macro variables having local scope. (Probably not feasible;
consider a macro utilizing a macro 'array' having thousands of elements)

--
Richard A. DeVenezia
Groeneveld, Jim
2003-11-24 15:26:24 UTC
Permalink
Hi Richard,

Another alternative with macros like you present, NOT containing any SAS code, only macro code, is to return the value of RC as the result of the macro call. Thus in your macro code at the end just specify:
&RC /* without semicolon */
and remove the RC argument from the parameter list;
and call your macro by:
%LET RC = %attrc (data=foo.bar, lib=libname, mem=memname);
Se a.o. http://listserv.uga.edu/cgi-bin/wa?A2=ind0310C&L=sas-l&P=R13877

Regards - Jim.
--
. . . . . . . . . . . . . . . .

Jim Groeneveld, MSc.
Biostatistician
Science Team
Vitatron B.V.
Meander 1051
6825 MJ Arnhem
Tel: +31/0 26 376 7365
Fax: +31/0 26 376 7305
***@Vitatron.com
www.vitatron.com

My computer has the solutions, I have the problems.

[common disclaimer]


-----Original Message-----
From: Richard A. DeVenezia [mailto:***@IX.NETCOM.COM]
Sent: Monday, November 24, 2003 15:31
To: SAS-***@LISTSERV.UGA.EDU
Subject: Macros that return values in specified macro variables


Sometimes a macro has to return a status value to the caller so the caller
can make a decision. Typically the status value is some sort of error code,
other times, the value is one of several that get set by the macro.

Consider this macro

%macro attrc (data=, lib=, mem=, rc=);
%local dsid;
%let dsid = %sysfunc (open (&data));
%if &dsid %then %do;
%let &lib = %sysfunc (attrc(&dsid,LIB));
%let &mem = %sysfunc (attrc(&dsid,MEM));
%let &rc = 0;
%end;
%else %do;
%let &rc = -1;
%end;
%mend;

options nosource;

%let libname=;
%let memname=;
%let rc=;
%let xrc=;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;

%attrc (data=sashelp.class, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;

%attrc (data=foo.bar, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;


The invocations have the log showing
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
xrc=0
libname=SASHELP
memname=CLASS
xrc=-1


One first blush I would want to say
%if &rc eq 0 %then <ok to proceed with libname and memname>

So, why did the first two invocations show rc = blank ?
Because the macro variable specified to contain the return value is also the
name of a macro variable local in scope to the macro invoked.

Thus I posit:

What is a good strategy to prevent variable name collision in macros that
are supposed to set macro variables in scope above themselves ?

Worst case I would want the macro called to generate an ERROR message to the
log if it is request to place a value in a macro variable that will be local
when the macro is running.

Is there a programmatic way to determine which macro variables are currently
defined at local scope (without going to SASHELP.VMACRO)?

Perhaps something like

%if %isLocal ( &rc ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;
...
%let &rc = < some return code value >;

or

%if &rc %in ( &_LOCALS_ ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;

where macro variable _LOCALS_ is a reserved automatic macro variable listing
all the macro variables having local scope. (Probably not feasible;
consider a macro utilizing a macro 'array' having thousands of elements)

--
Richard A. DeVenezia
Fehd, Ronald J. PHPPO
2003-11-24 15:30:36 UTC
Permalink
Post by Richard A. DeVenezia
Sometimes a macro has to return a status value to the caller
so the caller can make a decision. Typically the status
value is some sort of error code, other times, the value is
one of several that get set by the macro.
Yes, this is a major problem.

The solution is to either write the macro as a function
so that the usage is:
%Let Value = %attrc(data = X
,lib = work
);
in which case attrc has %local parameter ReturnValue
and ends with
&ReturnValue.%Mend;

or pass the name of the macro var as a parameter
-- meaning the macro is written as a procedure:
%Let Value = ;
%%attrc(data = X
,lib = work
,return = value
);

macro attrc would have a statement such as
%Let &Return. = <value calculation>;

of course, as you've discovered below
naming conventions are very important
so that your use of the parameter assignment
rc=rc
generates code which assigns the value to return to a %local variable.
thus negating your Good Intentions.
Does Good Practice require one to have to test:
%If &Rc. eq &&&Rc. %then %do;
%put error: parameter RC has value RC;
%goto Exit;%end;

Peter Crawford wrote me in private e-mail that the global macro variable
SysIndex
can be used to generate unique names:
%Put SysIndex<&SysIndex.>;*unique integer in session;
%Let ValueName = value&SysIndex.;
%Let &ValueName. = ;
%%attrc(data = X
,lib = work
,return = &ValueName.
);
%Put SysIndex<&SysIndex.>;*unique integer in session;
%Put &ValueName.<&&&ValueName.>;

as you can see, it gets kludgy real fast! %-\

In my own R&D
I have arrived at the startling conclusion
that it is best to have a set of macro variable naming conventions
that differ for each of:
* open, global
* parameters for %includes
* primary macro usage
* secondary or fan-in macros,
low level macros called by numerous primary macros

currently they are:
* global testing mvars:
TestSite values in boolean: (0,1)
TestProj values in boolean: (0,1)
Source2 in source2 nosource2;
may change this to either TestSource2
or ShowSource2
and perhaps add ShowSource3
* parameters of %Includes:
LibRef
LibRefIn
LibRefOut
Data_Set

* parameters of macros:
Lib_In
Lib_Out
Data
Var
Testing, see global TestSite

this allows me to write:
%DoThis(data = &Data_set.)

as you can see by searching the archives
folks have been majorly burned by name collision problems
such as you're examining

one has only to wonder why folks use *-more-than-* one underscore
for the prefix of all their %local macro var names.

Ron Fehd the macro maven CDC Atlanta GA USA ***@cdc.gov
If you want creative workers,
give them enough time to play.
-- John Cleese, comic actor (1939- )

or: give them enough time to parse parameters
-- RJF2
Post by Richard A. DeVenezia
Consider this macro
%macro attrc (data=, lib=, mem=, rc=);
%local dsid;
%let dsid = %sysfunc (open (&data));
%if &dsid %then %do;
%let &lib = %sysfunc (attrc(&dsid,LIB));
%let &mem = %sysfunc (attrc(&dsid,MEM));
%let &rc = 0;
%end;
%else %do;
%let &rc = -1;
%end;
%mend;
options nosource;
%let libname=;
%let memname=;
%let rc=;
%let xrc=;
%attrc (data=sashelp.class, lib=libname, mem=memname, rc=rc);
%put libname=&libname; %put memname=&memname; %put rc=&rc;
%attrc (data=foo.bar, lib=libname, mem=memname, rc=rc);
%put libname=&libname;
%put memname=&memname;
%put rc=&rc;
%attrc (data=sashelp.class, lib=libname, mem=memname,
rc=xrc); %put libname=&libname; %put memname=&memname; %put xrc=&xrc;
%attrc (data=foo.bar, lib=libname, mem=memname, rc=xrc);
%put libname=&libname;
%put memname=&memname;
%put xrc=&xrc;
The invocations have the log showing
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
rc=
libname=SASHELP
memname=CLASS
xrc=0
libname=SASHELP
memname=CLASS
xrc=-1
One first blush I would want to say
%if &rc eq 0 %then <ok to proceed with libname and memname>
So, why did the first two invocations show rc = blank ?
Because the macro variable specified to contain the return
value is also the name of a macro variable local in scope to
the macro invoked.
What is a good strategy to prevent variable name collision in
macros that are supposed to set macro variables in scope
above themselves ?
Worst case I would want the macro called to generate an ERROR
message to the log if it is request to place a value in a
macro variable that will be local when the macro is running.
Is there a programmatic way to determine which macro
variables are currently defined at local scope (without going
to SASHELP.VMACRO)?
Perhaps something like
%if %isLocal ( &rc ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;
...
%let &rc = < some return code value >;
or
%if &rc %in ( &_LOCALS_ ) %then %do;
%put ERROR: &rc can not be used to return a value;
%return;
%end;
where macro variable _LOCALS_ is a reserved automatic macro
variable listing all the macro variables having local scope.
(Probably not feasible; consider a macro utilizing a macro
'array' having thousands of elements)
--
Richard A. DeVenezia
Loading...