【问题标题】:SAS: Calling macro from within a Data step loopSAS:从数据步骤循环中调用宏
【发布时间】:2024-01-22 18:32:01
【问题描述】:

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

%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;

所有这些似乎都是处理基本编程任务的迂回方式。我希望更多地了解数据步骤方法失败的原因能让我深入了解如何更优雅地完成这项任务。

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

【问题讨论】:

    标签: macros sas abstraction


    【解决方案1】:

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

    宏循环方法是完全合理的,我认为其他编程语言基本上是你会做的。在 C# 中编写一个方法 draw_box 在屏幕上显示一个框,然后编写一个方法 draw_three_boxes 通过调用 draw_box 三次,在屏幕上显示三个框。

    现在,看起来很傻的原因是你的编程设计很糟糕,因为draw_three_boxes 方法非常有限:它只能做一件事,画三个盒子,那你为什么不做原来的draw_box 方法首先做到这一点?

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

    而是要弄清楚为什么要运行它三遍。如果它真的是你刚刚知道的东西,而不是一段数据,那么,写%PerformDoLoop(count=),然后调用%PerformDoLoop(count=3)。或者在原宏中包含%do循环,带个参数为count,默认为1。

    更可能的是,这样做 3 次是出于数据驱动的原因。你有 3 个州。你有 3 个班级的学生。任何。使用它来生成对%BlackBox 的调用。这会给你最好的结果,因为你没有在程序中这样做 - 你的数据发生变化,你立即得到 2 或 4 或任何调用。

    您可以查看我最近发表的论文,Writing Code With Your Data,来自 SESUG 2016,以了解有关如何执行此操作的更多信息。

    【讨论】:

    • 一如既往,谢谢你,乔。为了解释我的行为,宏 %PerformDoLoop 并不是要类似于编写执行循环的方法。它实际上是执行循环本身。在任何其他语言中,可以在开放代码中调用循环。在 SAS 中并非如此。相反,循环必须包含在 data _null_ 或上面的宏修复调用块中。实际上,我的 BlackBox 需要一个参数。由于我需要为不同的参数调用 BlackBox,因此我创建了一个宏列表(作为伪数组)。然后使用数组为 BlackBox 提供不同的参数并执行循环索引。
    • @LoremIpsum 如果您要这样做,那么我不会编写循环宏 - 而不是创建参数的宏列表,只需创建一个调用 BlackBox 的宏列表。我还建议 SAS 在开放代码中没有循环并不是特别独特。你也不能在 C 中做到这一点,对于一个特别明显的例子,它必须在一个子例程中。 SAS 也一样,它只是强制代码封装。也就是说,在 SAS 9.5 中,它们看起来允许宏之外的宏流控制(%do 等),尽管我不相信这是一件好事。
    • 9.5左右的有趣点。我在 SGF 听说 %IF 将在 9.5 中发布。并同意我也不确定这是一件好事。你有没有看到任何关于如何实施的早期文章?
    • @Quentin 不,我没有(今年我错过了 SGF,在 SESUG 上没有看到任何关于它的信息)。
    【解决方案2】:

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

    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 行的 data _null_ 结束了第一个数据步骤,将其留在未封闭的 do 块中。

    我同意@Joe 的观点。如果要生成多个宏调用,使用宏 %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.
    

    【讨论】: