Macros SAS:从数据步循环中调用宏

Macros SAS:从数据步循环中调用宏,macros,sas,abstraction,Macros,Sas,Abstraction,为了重构一个程序,我采用了一个我想抽象的复杂过程,并将其放在一个宏中 %macro BlackBox(); data _null_; put "This represents a complex process I want to abstract."; run; %mend; 该过程需要连续发生多次,因此显而易见的解决方案是将其放在一个循环中 data _null_; do i = 1 to 3; %BlackBox(); end; run; 但是,这会产生

为了重构一个程序,我采用了一个我想抽象的复杂过程,并将其放在一个宏中

%macro BlackBox();
  data _null_;
    put "This represents a complex process I want to abstract.";
  run;
%mend;
该过程需要连续发生多次,因此显而易见的解决方案是将其放在一个循环中

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;
但是,这会产生以下错误

ERROR 117-185: There was 1 unclosed DO block.
发生了什么事

我的最佳猜测是SAS正在尝试在数据步骤中运行数据步骤

我发现可以通过将循环包含在宏中,然后立即调用宏来避免此错误

%macro PerformDoLoop();
  %do i = 1 %to 3;
    %BlackBox();
  %end;
%mend;
%PerformDoLoop;
所有这些似乎都是处理基本编程任务的一种迂回方式。我希望更多地了解数据步方法失败的原因,能够让我深入了解如何更优雅地完成这项任务


请理解,这是一个用于说明我遇到的错误的简化示例。宏的实际实例可能包含参数或返回值。

您的假设完全正确;SAS试图在一个数据步骤中执行一个数据步骤,当然,这不会发生在任何地方(当然,这是可能的,但只是……复杂地)

宏循环方法是完全合理的,我认为其他编程语言基本上就是这样做的。您可以编写一个方法
draw_box
以C#的形式在屏幕上显示一个框,然后通过调用
draw_box
三次,编写一个方法
draw_box
,在屏幕上显示三个框

现在,它看起来很愚蠢的原因是你的编程设计很糟糕,因为
draw\u-three\u-box
方法非常有限:它只能做一件事,即画三个框,那么你为什么不让原来的
draw\u-box
方法一开始就这样做呢

大概,你应该写
draw\u box
,然后写
draw\u box(int count,int xpos,int ypos)
或者类似的东西,对吧?这里也一样。您不应该像以前那样编写
performdolop()
宏,因为您正在硬编码执行循环的次数

相反,要弄清楚为什么要运行它三次。如果它确实是您刚刚知道的东西,而不是一段数据,那么,编写
%performdolop(count=)
,然后调用
%performdolop(count=3)
。或者在原始宏中包含
%do
循环,并带有一个count参数,默认为1

更有可能的是,有一个数据驱动的理由要做三次。你有3个州。你们有三班学生。无论什么使用它生成对
%BlackBox
的调用。这将给你最好的结果,因为你没有在程序中这样做-你的数据改变,你立即得到2或4或任何调用


你可以看到我最近发表的论文,来自SESUG 2016,了解更多关于如何做到这一点的信息。

你的假设完全正确;SAS试图在一个数据步骤中执行一个数据步骤,当然,这不会发生在任何地方(当然,这是可能的,但只是……复杂地)

宏循环方法是完全合理的,我认为其他编程语言基本上就是这样做的。您可以编写一个方法
draw_box
以C#的形式在屏幕上显示一个框,然后通过调用
draw_box
三次,编写一个方法
draw_box
,在屏幕上显示三个框

现在,它看起来很愚蠢的原因是你的编程设计很糟糕,因为
draw\u-three\u-box
方法非常有限:它只能做一件事,即画三个框,那么你为什么不让原来的
draw\u-box
方法一开始就这样做呢

大概,你应该写
draw\u box
,然后写
draw\u box(int count,int xpos,int ypos)
或者类似的东西,对吧?这里也一样。您不应该像以前那样编写
performdolop()
宏,因为您正在硬编码执行循环的次数

相反,要弄清楚为什么要运行它三次。如果它确实是您刚刚知道的东西,而不是一段数据,那么,编写
%performdolop(count=)
,然后调用
%performdolop(count=3)
。或者在原始宏中包含
%do
循环,并带有一个count参数,默认为1

更有可能的是,有一个数据驱动的理由要做三次。你有3个州。你们有三班学生。无论什么使用它生成对
%BlackBox
的调用。这将给你最好的结果,因为你没有在程序中这样做-你的数据改变,你立即得到2或4或任何调用


您可以查看我最近在SESUG 2016上发表的论文,了解有关如何做到这一点的更多信息。

宏语言是一种预处理器。它生成SAS代码,并在编译数据步骤代码之前执行。使用您的代码:

data _null_;
  do i = 1 to 3;
    %BlackBox();
  end;
run;
宏%BlackBox()将执行一次(不是三次,因为它在DO循环执行之前执行,概念上在DO循环之外)。并且数据步骤代码变为:

data _null_;
  do i = 1 to 3;
    data _null_;
    put "This represents a complex process I want to abstract.";
    run;
  end;
run;
正如您所说,在SAS中,不可能在另一个数据步骤中执行数据步骤。第3行上的
数据\u null
结束第一个数据步骤,将其保留在未关闭的do块中

我同意乔的观点。如果要生成大量宏调用,使用宏%DO循环是一种很好的方法。他的论文给出了一种使用数据生成宏调用的好方法,通过构建解析为宏调用列表的宏变量

另一个有用的学习方法是调用执行。这允许您使用数据步骤生成宏调用。CALL EXECUTE在执行数据步骤时生成宏调用,宏将在数据步骤之外执行(当您使用%NRSTR时,如下所示)。例如:

data _null_;
  do i = 1 to 3;
    call execute ('%nrstr(%BlackBox())');
  end;
run;
威尔
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.