【问题标题】:Why aren't SAS Macro Variables Local-Scope by Default?为什么默认情况下 SAS 宏变量不是本地范围的?
【发布时间】:2016-02-21 07:02:05
【问题描述】:

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

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

  • 如果全局符号表中没有“x”,则创建一个局部范围的宏变量 x,或者
  • 如果“x”在全局符号表中,则更新全局范围宏变量“x”

这让我觉得非常不直观。由于这种设计选择,我愿意打赌 SAS 荒野中存在大量错误。我很少在宏中看到 %local 语句,即使是在使用常见变量名(如“i”或“counter”)的循环语句之上。例如,我刚刚从 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 宏变量范围的工作方式是否有充分的理由?

【问题讨论】:

  • 这是一个有趣的问题,但对于 s.o. 来说可能是题外话。因为现在可能有一个正确的答案,它接近舆论。建议在communities.sas.com 或SAS-L 询问,两者都有更多讨论。也就是说,我同意你的观点,范围规则并不直观,并且很可能会导致错误。
  • 感谢昆汀的建议。根据“语言设计”标签的描述,以及我在这里看到的关于该标签的问题类型,我认为这个问题不会偏离主题。不过,也许我也会将其发布在您提到的网站之一上。
  • 关于语言设计标签的好点,我会假设其中许多问题也是题外话。 (即使我认为是OT,我还是要写一个答案。:)
  • 作为一名每天生活在“荒野”中的顾问,我可以确认这确实是一个问题。它允许人们创建很难调试的糟糕代码。我最近对一位同事说,我花了 80% 的编码时间来处理其他代码,试图找到宏变量的设置位置。

标签: scope sas language-design dynamic-scope


【解决方案1】:

很大程度上,因为 SAS 是一种已有 50 年历史的语言,早于 lexical scoping 显然是首选。

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

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

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

【讨论】:

  • 感谢乔的洞察力。我能否请您澄清一下您的意思是 SAS“不提供在更高范围内有意更改变量的能力......除了 %global 吗?”在我的问题的代码中,如果宏定义上方有一个“%let j = 5”语句,那么 %flag 宏将在没有 %global 语句的情况下更改更高范围 &j 的值。大概有时这是故意的。
  • CALL SYMPUTX() 函数允许您覆盖写入最本地定义的宏变量的默认行为,但只能写入GLOBAL 宏范围。您不能使用它来写入任意中间范围级别。
  • @Max,这正是因为 SAS 是动态范围的 - 但这不是您可以在需要时“打开”的东西。您可以强制变量为局部范围(实际上是词法范围),但您不能强制变量的局部范围较小但不是全局 - 因此您不能强制它处于中间范围。
【解决方案2】:

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

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

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

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

【讨论】:

【解决方案3】:

然后我们不能这样做,或者至少不能没有新的声明性声明。

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

【讨论】:

    猜你喜欢
    • 2021-11-21
    • 2010-11-02
    • 2016-04-22
    • 1970-01-01
    • 1970-01-01
    • 2018-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多