为什么默认情况下没有 SAS 宏变量 Local-Scope?

Why aren't SAS Macro Variables Local-Scope by Default?

我在尝试解决与宏变量作用域相关的问题时发现这个非常有用的 SO 页面。 why doesn't %let create a local macro variable?

总结一下,在宏中写 %let x = [];%do x = [] %to []; 将:

这让我觉得非常 non-intuitive。由于这种设计选择,我愿意打赌在 SAS 荒野中存在大量错误。我很少在宏中看到 %local 语句,甚至在使用 "i" 或 "counter." 等常见变量名的循环语句之上 例如,我刚刚提取了第一篇标题中带有单词 "macro" 的论文来自这份 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 变量的人都会有祸了。他们很容易以没有日志错误而结束,但结果是虚假的,因为在他们调用 %flag 之后,他们的 &j 到处都是 4,这将是(根据经验)一个追踪起来没有乐趣的错误。或者更糟的是,他们可能永远不会意识到他们的结果是假的。

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

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

当我学习宏语言时(在 6.12 上),我很幸运从一开始就被教导宏应该始终将它们的变量声明为 %LOCAL,除非它们有充分的理由不这样做。有时,如果宏 var 未声明为 %local 或 %global,我什至会在其中添加 /* Not Local: MyMacVar */ 注释以证明我无意声明范围(这不常见但有时很有用)。看到未将变量声明为 %LOCAL 的 UG 论文、SO 答案等让我很痛苦。

我猜测(这只是一个猜测),有一些早期版本的 SAS 在代码中有用于文本生成的(全局)宏变量,但没有宏。所以在这样的版本中,人们会习惯于拥有大量的全局宏变量,以及相关的问题(例如碰撞)。那么SAS在设计宏的时候,问题就出来了,"Can I reference my macro vars from inside a macro?"而设计者选择回答"yes, not only can you reference them, you can also assign values to them, and I'll make it easy by allowing you to do that by default. But also, a macro will create its own scope that can hold local macro variables. If you reference a macro var or assign a macro var that with the same name as a macro var that exists in a global scope (or any outer scope), I'll assume you are referencing the global macro variable (like you are used to already), unless you have explicitly declared the macro var to be %LOCAL."

从目前的宏语言/宏开发者的角度来看,大多数人认为应该避免使用大多数全局宏变量。宏语言的好处之一是它提供了允许 modularization/encapsulation/information-hiding 的宏。从这个角度来看,%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

很大程度上,因为 SAS 是一种已有 50 年历史的语言,它在 lexical scoping 明显受到青睐之前就已经存在。

SAS 混合了两种范围界定概念,但除非您有意更改它,否则大部分是动态范围界定的。这意味着仅通过阅读函数的定义,您无法判断在 运行 时间哪些变量可用;和赋值语句适用于当前在 运行 时间可用的变量版本(而不是强制在可用的最本地范围内)。

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

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