Thanks @FriedEgg.
[Sorry, this is probably TL;DR... ]
Agree, by "surprise" I meant something like "departure from the scoping rules that would be applied outside of the DOSUBL setting." And of course, it may be reasonable for DOSUBL to have different scoping rules. Definitely huge improvement over previous earlier versions. Mostly I'm playing with this now because I finally got 9.4M4 and I really want to convince myself to start using it in production work.
Also agree, that it is a key point that when called via DOSUBL, my %INNER macro is not actually executing nested inside of %TEST, it is executing at execdepth=1 in the side session. But I think it could still be improved to honor the local scope of %INNER when DOSUBL returns macro variables. Consider...
#3.
%macro Test(dummy=);
%local A ;
%let rc=%sysfunc(dosubl(%nrstr(
%let A=DoSubl_A;
%put >>%sysmexecdepth<<;
%put ***Inside DOSUBL***;
%put _user_;
)));
%put ***Outside DOSUBL***;
%put _user_;
%mend Test;
%let A=Global_A;
%Test()
Returns:
>>0<<
***Inside DOSUBL***
GLOBAL A DoSubl_A <-- Global symbol tabe of DOSUBL session
TEST A <-- Local symbol tabe of main session
TEST DUMMY <-- Local symbol tabe of main session
GLOBAL A Global_A <-- Global symbol tabe of main session
***Outside DOSUBL***
TEST A DoSubl_A <-- Local symbol tabe of main session
TEST DUMMY <-- Local symbol tabe of main session
TEST RC 0 <-- Local symbol tabe of main session
GLOBAL A Global_A <-- Global symbol tabe of main session
I'm happy with the above behavior. As I see it, the %LET runs in open code in the DOSUBL session. It creates a Global macro varible A in the DOUSBL session global symbol table. Note that it doesn't actually write to the Global macro variable A that already exists in the main session global symbol table. When you are in the DOSUBL session, you can see both global macro variables named A. When DOSUBL session completes, it needs to decide where to return its global macro variable A, and when it sees there is a local macro variable A, it returns it there. This is all good.
#4
%macro Test(dummy=);
%let rc=%sysfunc(dosubl(%nrstr(
%inner()
)));
%put ***Outside DOSUBL***;
%put _user_;
%mend Test;
%macro inner(dummy);
%let A=DoSubl_A;
%let B=DoSubl_B;
%put ***Inside DOSUBL***;
%put _user_;
%mend;
%symdel A B;
%Test()
If neither A nor B exist in a main session scope, when %INNER excutes it makes both A and B as %LOCAL to inner, which is reasonable. Nothing is returned to the main session. I like that.
#5
%macro Test(dummy=);
%let rc=%sysfunc(dosubl(%nrstr(
%inner()
)));
%put ***Outside DOSUBL***;
%put _user_;
%mend Test;
%macro inner(dummy);
%let A=DoSubl_A;
%let B=DoSubl_B;
%put ***Inside DOSUBL***;
%put _user_;
%mend;
%symdel A B;
%global A;
%let A=Global_A;
%Test()
Here we make a global variable A in the main session. It's not obvious if this will change any results, but it does (in a good way). Now, when %INNER executes in the DOSUBL session, I think it looks to main session global scope, and sees that there is a main session Global variable named A. Because of that, it makes a DOSUBL session global macro variable A instead of making A local to inner. It makes macro variable B local to INNER because it didn't exist in the main session global table. When DOSUBL completes, the DOSUBL global macro variable A is returned to the main session global macro variable A. Nice.
#6
%macro Test(dummy=);
%local A;
%let rc=%sysfunc(dosubl(%nrstr(
%inner()
)));
%put ***Outside DOSUBL***;
%put _user_;
%mend Test;
%macro inner(dummy);
%let A=DoSubl_A;
%let B=DoSubl_B;
%put ***Inside DOSUBL***;
%put _user_;
%mend;
%symdel A B;
%Test()
This is the disappointing case. A is local in the main session, but does not get populated when DOSUBL completes. I think when the %LET A= statement in %INNER executes it checks to see if there is a main session global macro variable named A, and seeing none, decides to make A local to %INNER. And therefore it is not returned to the main session.
I think the fix would be to have the %LET A= statement first check to see if there is a main session LOCAL macro variable named A. If there is, it should make A global in the DOSUBL environment, and then when DOSUBL completes it would return the DOSUBL session global variable to the main session local symbol table. (Just like it did in example #3) Note that if you add a %GLOBAL statement to %INNER it attempt to force A to be global, it will (reasonably) error, with this message that you can create it as global because it already exists in a local scope.
In my (still developing) thinking, it would be nice if the rules of %LET when executed in a macro in a DOSUBL session were:
1. Does the macro variable exist in a DOSUBL symbol table (local or global)? If yes, write it in inner most table where it exists. Else continue.
2. Does the macro variable exist in a Main session global symbol table or main session local symbol table? If yes, write it to DOSUBL session global symbol table. Else continue.
3. Create the macro variable in the current scope where the %LET is executing.
Then when DOSUBL completes it would return any DOSUBL global macro variables to the main session. And it would follow the "usual" scoping rules when doing so, i.e. starting from the inner most scope, look in each scope to see if the macro variable exists. If it exists already, update it in the inner most scope it is found. If it does not exist anywhere, create it in the inner most scope.
(Note that I'm intentionally avoiding thinking about nested DOSUBL environments, cuz I'm not ready for that yet 🙂
I think the current rules are pretty close to that, except for example #6 which shows that the main session local table is not checked when the %LET statement executes, and example #1 which shows that if a macro variable does not exist in any main session symbol table, DOSUBL will return variables to the main session global table instead of the inner most scope that exists.
Thanks again!
... View more