0

To refactor a program, I took a complex process I want to abstract and placed it within a macro.

%macro BlackBox();
  data _null_;
    put "This represents a complex process I want to abstract.";
  run;
%mend;

The process needs to occur multiple times in succession, so the obvious solution is to place it within a loop.

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;

This, however, produces the following error.

ERROR 117-185: There was 1 unclosed DO block.

What is happening?

My best guess is that SAS is trying to run a data step within a data step.

I find that I can avoid this error by enclosing my loop within a macro and then immediately calling the macro.

%macro PerformDoLoop();
  %do i = 1 %to 3;
    %BlackBox();
  %end;
%mend;
%PerformDoLoop;

All this seems like an roundabout way of handling a fundamental programming task. I'm hoping that learning more about why the data step approach fails will give me insight into how to complete this task more elegantly.

Please understand that this is a simplified example used to illustrate the error I am encountering. A real instance of the macro may take in arguments or return values.

2 Answers 2

2

The macro language is a pre-processor. It generates SAS code, and it executes before the DATA step code is even compiled. With your code:

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;

The macro %BlackBox() will execute once (not three times, because it executes before the DO loop executes, conceptually outside of the DO loop). And the data step code becomes:

data _null_;
  do i = 1 to 3;
    data _null_;
    put "This represents a complex process I want to abstract.";
    run;
  end;
run;

As you say, it is not possible in SAS to execute a data step inside another data step. The data _null_ on line 3 ends the first data step, leaving it within an unclosed do block.

I agree with @Joe's points. If you want to generate a number of macro calls, using a macro %DO loop to do so is a fine approach. His paper gives a nice approach for using data to generate macro calls, by building macro variables that resolve to a list of macro calls.

Another useful approach to learn is CALL EXECUTE. This allows you to use a data step to generate macro calls. CALL EXECUTE generates the macro calls when the data step executes, and the macros will execute outside of the data step (when you use %NRSTR as below). For example:

data _null_;
  do i = 1 to 3;
    call execute ('%nrstr(%BlackBox())');
  end;
run;

Will generate three macro calls:

NOTE: CALL EXECUTE generated line.
1   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.

2   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.

3   + %BlackBox()
MPRINT(BLACKBOX):   data _null_;
MPRINT(BLACKBOX):   put "This represents a complex process I want to abstract.";
MPRINT(BLACKBOX):   run;

This represents a complex process I want to abstract.
Sign up to request clarification or add additional context in comments.

Comments

2

Your assumption is precisely correct; SAS is trying to execute a data step within a data step, and that's not going to go anywhere of course (well, it's possible, but only ... complexly).

The macro loop method is entirely reasonable, and I would argue for other programming languages that's basically what you'd do. You write a method draw_box to display a box on the screen in C#, then you write a method draw_three_boxes that displays three boxes on the screen by calling draw_box three times.

Now, the reason it seems silly is that you have bad programming design insomuch as the draw_three_boxes method is very limited: it's only capable of doing one thing, drawing three boxes, so why didn't you just make the original draw_box method do that in the first place?

Presumably, what you should want to do is write draw_box and then write draw_boxes(int count, int xpos, int ypos) or something like that, right? Same thing here. You shouldn't write the PerformDoLoop() macro as you did because you're hardcoding the number of times to perform the loop.

Instead get at why you are running it three times. If it's really something you just know and isn't a piece of data, well, write %PerformDoLoop(count=) and then call %PerformDoLoop(count=3). Or include the %do loop in the original macro, with a parameter for count, default it to one.

More likely, there is a data driven reason for doing it 3 times. You have 3 states. You have 3 classes of students. Whatever. Use that to generate the calls to %BlackBox. That will give you the best results, because then you aren't doing it in the program - your data changes, you instantly get 2 or 4 or whatever calls.

You can see my recently presented paper, Writing Code With Your Data from SESUG 2016 for more information on how to do that.

4 Comments

As always, thank you, Joe. To explain my actions, the macro %PerformDoLoop is not intended to be analogous to writing a method to perform the loop. It is literally to perform the loop itself. In any other language, a loop may be called in open code. Not so in SAS. A loop instead must either be wrapped in a data _null_ or within a macro-mend-call block as above. In reality, my BlackBox takes an argument. Since I need to call BlackBox for different arguments, I have created a macro list (as a pseudo-array). BlackBox is then fed a different argument using the array and do loop index.
@LoremIpsum If you're going to do that, then I would not write the loop macro - instead of creating the macro list of parameters, just create a macro list of calls to BlackBox. I'd also suggest that SAS is not particularly unique in not having loops in open code; you couldn't do that in C either, for a particularly obvious example, it would have to be in a subroutine. Same for SAS, it's just enforced code encapsulation. That said, in SAS 9.5 it looks like they will allow the macro flow control (%do etc.) outside of macros, though I'm not convinced that's a good thing.
Interesting point about 9.5. I had heard at SGF that open statement %IF was coming in 9.5. And agree I'm not sure it's a good thing, either. Have you seen any early write ups on how it will be implemented?
@Quentin Nope, I haven't (I missed SGF this year, and didn't see anything at SESUG about it).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.