DATA Step, Macro, Functions and more

DOSUBL scope of returned macro variables in 9.4M4

Reply
PROC Star
Posts: 1,325

DOSUBL scope of returned macro variables in 9.4M4

[ Edited ]

Hi All,

I've been playing with DOSUBL in 9.4M4 on linux, and happy to see that the scope collision discussed in https://communities.sas.com/t5/Base-SAS-Programming/DOSUBL-scope-of-returned-macro-variables/m-p/138... is gone. 

 

So far, I found two scope issues which are surprising to me. I'm not prepared to call them bugs, but curious if people think this is expected/reasonable behavior.

 

#1

If I code

%macro Test(dummy=);
  /*%local rc A;*/  

  %let rc=%sysfunc(dosubl(%nrstr(
    %let A=DoSubl_A;
  )));

  %put _user_;
%mend Test;

%test()

I would expect the macro variable A to be created in the local scope of Test (assuming it does not already exist in global symbol table), even without a %local statement.  But it is returned to the main session global symbol table.   That surprises me, but if you add the %local statement, it works like I want. So this doesn't bother me too much.

 

 

#2

If you code:

%macro Test(dummy=);
   %global A;
   %local B; 
   %let A=Global_A;
   %let B=Local_B;
  
   %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()


 

I would expect that after DOSUBL completes, the macro variable A would be returned to the main session global symbol table, and the macro variable B would be returned to the local symbol table of %Test.  The macro variable A works as I expect, but the macro variable B is not returned to the local symbol table of %Test.  It looks like it is created as local to %Inner, and therefore is not returned to the main session at all.  This one bothers me a bit more.  It would be nice to be able to call a macro inside a DOSUBL block, and have it return macro variables to a local symbol table in the main session.

 

Thanks for any thoughts,

--Q.

Trusted Advisor
Posts: 1,301

Re: DOSUBL scope of returned macro variables in 9.4M4

#1 I don't feel is surprising. The B macro variable inside the dosubl session is in the global scope and is not defined elsewhere, so it is transferred back into the global scope. The fact that it will transfer the value to a local scope, if it exists also conforms with expectations. It is a departure from the expectations of defining the variable with a %let statement inside a macro without dosubl, which would default to the local scope though.

#2 Like in the first example, we have a disconnect here between expected behavior without dosubl and with it.


I think, ideally, the behavior of the macro variable transfer should respect the behaviors without using dosubl but I think this is a good step forward from previous behavior. I wouldn't think of this as a bug but I do think the further improvements could be made.

I feel that the reasoning behind the issue displayed by your example #2 can be best explained by looking at the %sysmexecdepth function output.

 

%macro outer;
%put OUTER: %sysmexecdepth %sysmexecname(%sysmexecdepth);
%inner
%let rc=%sysfunc(dosubl(%nrstr(%inner)));
%mend;

%macro inner;
%put INNER: %sysmexecdepth %sysmexecname(%sysmexecdepth);
%mend;

%outer
OUTER:1
INNER:2
INNER:1

As you can see, the execution of the inner macro by the dosubl doesn't respect the nest level of the macro to the OUTER macro.  This would be a departure from the behavior without using dosubl and is a reasonable explaination for why the value does not move up in scope.

PROC Star
Posts: 1,325

Re: DOSUBL scope of returned macro variables in 9.4M4

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 Smiley Happy

 

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!

Ask a Question
Discussion stats
  • 2 replies
  • 139 views
  • 2 likes
  • 2 in conversation