Hi,
If I submit the following code in a SAS session (9.4M4 and 9.4M6), %PUT _USER_ shows that there are two different macro variables named A:
%let A=Global_A;
%macro try() ;
data _null_ ;
rc=dosubl('%let A=DoSubl_A ; %put _user_ ;' ) ;
run ;
%mend try ;
%try()
Returns:
161 %try() GLOBAL A DoSubl_A GLOBAL A Global_A
Question #1: I think this suggests that the side session has it's own global symbol table. And %PUT _USER_ is showing the content of both the main session global symbol table and the side session symbol table. Do you agree?
Interestingly, if DoSubl is called outside of a macro, the results are different:
%let A=Global_A;
data _null_ ;
rc=dosubl('%let A=DoSubl_A ; %put _user_ ;' ) ;
run ;
returns:
GLOBAL A DoSubl_A
I think when DOSUBL is called outside of a macro, when the side-submitted %LET statement executes, it actually updates the value of A in the main session global symbol table, rather than create a new macro variable A in the side session global symbol table.
Question #2: Any idea *why* these two bits of code should return different results?
Thanks,
-Q.
My understanding:
> side session has it's own global symbol table
Yes it does. And that global table is deleted at then end of the Sub session.
The way I see it, when dosubl() is called from inside a macro, it has write access to the macro symbol table, and read access to the global table.
When dosubl() is called from outside of a macro, it has write access to the global symbol table.
Look:
%let a=Global_A;
%let b=Global_B;
%put --Start--; %put _USER_ ;
%macro try() ;
%local a;
%let a=Local_A;
%put --MacroStart--; %put _USER_ ;
data _null_ ;
rc=dosubl('%let a=DoSubl_A ; %put --DoSub--; %put _USER_ ;' ) ;
run ;
%put --MacroEnd--; %put _USER_ ;
%mend try ;
%try() ;
%put --End--; %put _USER_ ;
%let a=Global_A;
%put --Start--; %put _USER_ ;
data _null_ ;
rc=dosubl(' %put --Before--; %put _user_ ; %let a=DoSubl_A ; %put --After--; %put _user_ ;' ) ;
run ;
%put --End--; %put _USER_ ;
--Start--
GLOBAL A Global_A
GLOBAL B Global_B
--MacroStart--
TRY A Local_A
GLOBAL A Global_A
GLOBAL B Global_B
--DoSub--
GLOBAL A DoSubl_A
TRY A Local_A
GLOBAL A Global_A
GLOBAL B Global_B
--MacroEnd--
TRY A Global_A
GLOBAL A Global_A
GLOBAL B Global_B
--End--
GLOBAL A Global_A
GLOBAL B Global_B
--Start--
GLOBAL A Global_A
GLOBAL B Global_B
--Before--
GLOBAL A Global_A
GLOBAL B Global_B
--After--
GLOBAL A DoSubl_A
GLOBAL B Global_B
--End--
GLOBAL A DoSubl_A
GLOBAL B Global_B
Thanks @ChrisNZ , that's a really nice example.
The way I see it, when dosubl() is called from inside a macro, it has write access to the macro symbol table, and read access to the global table.
I see it differently. I think when dosubl() is called from inside a macro, it has read/write access to both the main session global table and the main session local macro symbol table. I added to your code a bit:
%symdel a b c /nowarn ;
%let a=Global_A;
%let b=Global_B;
%put --Start--; %put _USER_ ;
%macro try() ;
%local a;
%let a=Local_A;
%put --MacroStart--; %put _USER_ ;
data _null_ ;
rc=dosubl('%put --DoSub 1-- ; %put _user_ ; %let a=DoSubl_A ; %let b=Dosubl_B ; %let c=Dosubl_C ; %put --DoSub 2-- ; %put _USER_ ;' ) ;
run ;
%put --MacroEnd--; %put _USER_ ;
%mend try ;
%try() ;
%put --End--; %put _USER_ ;
Returns:
--MacroStart-- TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B --DoSub 1-- TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B --DoSub 2-- GLOBAL A DoSubl_A GLOBAL B Dosubl_B GLOBAL C Dosubl_C TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B --MacroEnd-- TRY A DoSubl_A GLOBAL A Global_A GLOBAL B Dosubl_B GLOBAL C Dosubl_C 533 %put --End--; %put _USER_ ; --End-- GLOBAL A Global_A GLOBAL B Dosubl_B GLOBAL C Dosubl_C
That shows that DOSUBL returned A to the local symbol table of %TRY, and returned B and C to the main session global symbol table. I think it would be better if C were returned to the local symbol table, but I can live with it.
@Quentin You results differ from mine.
If I run your code, the result is:
--Start--
GLOBAL A Global_A
GLOBAL B Global_B
--MacroStart--
TRY A Local_A
GLOBAL A Global_A
GLOBAL B Global_B
--DoSub 1--
TRY A Local_A
GLOBAL A Global_A
GLOBAL B Global_B
--DoSub 2--
GLOBAL A DoSubl_A
GLOBAL B Dosubl_B
GLOBAL C Dosubl_C
TRY A Local_A
GLOBAL A Global_A
GLOBAL B Global_B
--MacroEnd--
TRY A Global_A
GLOBAL A Global_A
GLOBAL B Global_B
GLOBAL C Dosubl_C
--End--
GLOBAL A Global_A
GLOBAL B Global_B
GLOBAL C Dosubl_C
SYSHOSTINFOLONG = X64_SRV12 WIN 6.2.9200 Server
SYSVLONG4 = 9.04.01M2P07232014
The differences you see could be cause by the different sas versions involved. I think that dosubl got an update with m3.
That would make sense. Upgrade as in fix by the looks of it. 🙂
There was a fix to DOSUBL macro var scoping in 9.4M4, that likely explains the difference. This thread discusses one bug that was fixed: https://communities.sas.com/t5/SAS-Programming/DOSUBL-scope-of-returned-macro-variables/td-p/138691
I haven't tested much in M6. I haven't heard of any changes yet. It's one of the challenges with not having the DOSUBL macro var scoping rules documented officially. It's sometimes hard to know what is the intended behavior we can count on to be maintained in future releases, and what behaviors might be changed in a future bug fix. Personally, I'm hoping that the 9.4M4 scoping behavior is stable/done, even though I disagree with some small aspects.
That idea of deleting main session global macro variables from within DOSUBL side session is very interesting. I can do that as well in 9.4M4. I think this means that when the docs say that macro variables are imported from main session to side session, it's not accurate. I think the side session can directly access the symbol tables of the main session.
I added a variable D to your code, that I delete inside the dosubl().
%symdel a b c /nowarn ;
%let a=Global_A;
%let b=Global_B;
%let d=Global_D;
%put --Start--; %put _USER_ ;
%macro try() ;
%local a;
%let a=Local_A;
%put --MacroStart--; %put _USER_ ;
data _null_ ;
rc=dosubl('%put --DoSub 1-- ; %put _user_ ; %let a=DoSubl_A ; %let b=Dosubl_B ; %let c=Dosubl_C ; %put --DoSub 2-- ; %symdel d; %put _USER_ ;' ) ;
run ;
%put --MacroEnd--; %put _USER_ ;
%mend try ;
%try() ;
%put --End--; %put _USER_ ;
I can create global variable C and delete global variable D, but not overwrite global variable B.
--Start-- GLOBAL A Global_A GLOBAL B Global_B GLOBAL D Global_D --MacroStart-- TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B GLOBAL D Global_D --DoSub 1-- TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B GLOBAL D Global_D --DoSub 2-- GLOBAL A DoSubl_A GLOBAL B Dosubl_B GLOBAL C Dosubl_C TRY A Local_A GLOBAL A Global_A GLOBAL B Global_B --MacroEnd-- TRY A Global_A GLOBAL A Global_A GLOBAL B Global_B GLOBAL C Dosubl_C --End-- GLOBAL A Global_A GLOBAL B Global_B GLOBAL C Dosubl_C
It's finally time to hack! Remember to visit the SAS Hacker's Hub regularly for news and updates.
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.