为什么 SAS 宏变量默认不是本地范围的?



我在尝试解决与宏变量范围相关的问题时发现了这个非常有用的 SO 页面。为什么 %let 不创建局部宏变量?

总而言之,在宏中编写%let x = [];%do x = [] %to [];将:

  • 如果全局符号表中没有"X",则创建局部范围的宏变量 X,或者
  • 如果全局符号表中有"X",则更新全局范围宏变量"X"

这让我觉得非常不直观。我敢打赌,由于这种设计选择,SAS荒野中存在大量错误。我很少在宏中看到 %local 语句,即使在使用"i"或"计数器"等常见变量名称的循环语句之上也是如此。例如,我刚刚从SUGI和SAS全球论坛论文列表中抽出了标题中带有"宏观"一词的第一篇论文。http://www.lexjansen.com/cgi-bin/xsl_transform.php?x=sgf2015&c=sugi

事实上,我在打开的第一篇SAS会议论文中找到了这段代码:

%macro flag;
data CLAIMS;
 set CLAIMS;
 %do j= 1 %to 3;
 if icd9px&j in (&codelist)
 then _prostate=1;
 %end;
run;
%mend;
%flag;

http://support.sas.com/resources/papers/proceedings15/1340-2015.pdf

任何调用 %flag 并且也有自己的 &j 变量的人都有祸了。他们很容易最终没有日志错误,而是虚假的结果,因为他们的 &j 在他们调用 %flag 后到处都是 4,这将是(根据经验)一个没有乐趣的错误。或者更糟糕的是,他们可能永远不会意识到他们的结果是假的。

所以我的问题是,为什么决定默认情况下不让所有宏变量都是局部范围?SAS 宏变量作用域的工作方式是否有充分的理由?

很大程度上,因为 SAS 是一种有 50 年历史的语言,在词汇范围明确成为首选之前就已经存在了。

SAS 混合了这两个作用域概念,但主要是动态作用域,除非有意更改它。 这意味着仅通过读取函数的定义,您无法知道在运行时可以使用哪些变量;赋值语句适用于当前在运行时可用的变量版本(而不是强制在最局部可用的范围内)。

这意味着宏编译器无法判断特定的赋值语句是要分配局部宏变量,还是运行时可能存在的更高范围的宏变量。 SAS 可以强制执行您所说的局部宏变量,但这会将 SAS 变成一种词法范围语言,无论是基于与过去的一致性(保持向后兼容性)还是基于功能,都不需要这种语言;SAS 提供了强制执行词法范围(使用 %local )的功能,但不提供在除%global之外的更高范围内故意更改变量(某种形式的parent?)的功能。

请注意,动态范围界定在 60 年代和 70 年代非常普遍。 S-Plus、Lisp等都有动态作用域。 SAS 倾向于尽可能向后兼容。 SAS也是常用的分析师,而不是程序员,因此需要尽可能避免复杂性。 它们为我们这些想要词汇范围优势的人提供了%local

在不了解宏语言的历史的情况下,回答为什么以这种方式定义范围规则对我来说很困难。

当我学习宏语言(在 6.12 上)时,我很幸运地从早期就被教导宏应该始终将其变量声明为 %LOCAL,除非他们有很好的理由不这样做。 有时,如果宏变量没有声明为 %local 或 %global,我什至会在其中放置一个/* Not Local: MyMacVar */注释来记录我不打算声明范围(这很不寻常,但有时很有用)。 看到 UG 论文、SO 答案等没有将变量声明为 %LOCAL,这让我很痛苦。

我猜(这只是一个猜测),有一些早期版本的 SAS 具有用于代码中文本生成的(全局)宏变量,但没有宏。 因此,在这样的版本中,人们会习惯于拥有大量的全局宏变量以及相关的问题(例如碰撞)。 然后,当SAS设计宏时,会出现一个问题,"我可以从宏内部引用我的宏变量吗?设计师选择回答"是的,您不仅可以引用它们,还可以为它们分配值,默认情况下,我将允许您这样做,从而使它变得容易。 而且,宏将创建自己的范围,可以保存局部宏变量。 如果您引用宏变量或分配与全局作用域(或任何外部作用域)中存在的宏变量同名的宏变量,我将假设您引用的是全局宏变量(就像您已经习惯的那样),除非您已明确声明宏变量为 %LOCAL。

从目前的宏语言/宏开发者的角度来看,大多数人认为应该避免大多数全局宏变量。宏语言的好处之一是它提供了允许模块化/封装/信息隐藏的宏。 从这个角度来看,%local变量更有用,而未声明为%local的宏变量是对封装的威胁(即冲突威胁)。 所以我倾向于同意,如果我重新设计宏语言,默认情况下我会使宏变量 %local。 但当然,在这一点上,改变为时已晚。

那么我们不能这样做,或者至少不能在没有新的声明性语句的情况下做到这一点。

33         %let c=C is global;
34         %macro b(arg);
35            %let &arg=Set by B;
36            %mend b;
37         %macro a(arg);
38            %local c;
39            %b(c);
40            %put NOTE: &=c;
41            %mend a;
42         %a();
NOTE: C=Set by B

最新更新