我輩はブロガーではない。ネタもまだない

SASとかDelphiあたりの人様の役に立たないネタを提供します

resolve関数の落とし穴

さて、マクロ変数に格納された値を取得したい時、どんな方法があるでしょうか。
通常は、&で展開、symget、resolve関数のいずれかを使うと思いますが、落とし穴があるのでご紹介。

まぁ変なコード例ばかり(こんなコード書かねぇよ、って?書いたのでこの問題に遭遇したのですよ)ではありますけど、知っておけば防げるので…。

%let X=abc;
data _null_;

  a=symget('X');    /* symgetでマクロ変数取り出し */
  b="&X";           /* &でマクロ変数取り出し */
  c=resolve('&X');  /* resolve関数でマクロ変数取り出し */
  put a b c;

  %let Y=DEF;
  a=symget('Y');
  b="&Y";
  c=resolve('&Y');
  put a b c;

  call symput('Z','xyz');
  a=symget('Z');
  b="&Z";
  c=resolve('&Z');
  put _all_;
  put a b c;

run;

上記を実行すると、こんな結果になります
f:id:japelin:20211122092804p:plain

1件WARNINGが出ていますね。

実はcall symputで生成したマクロ変数は、dataステップが終了しないと、そのマクロ変数にアクセスできないのです。
(ここは落とし穴でもなんでもなく、)まぁこんなことは皆さんご存知だと思うんですが、symgetとresolveだと、正しく取得できます。コレ結構便利では?
(なんとなくですが、PDV内にマクロ変数用のバッファ領域みたいのがあって、データステップ関数ならそこにアクセスできる、というイメージ。正しいかどうかはわかりませんが)


とまぁ、前置きはこれくらいにして、落とし穴の話を。

以下の2つのマクロを見てください。

%Macro Mtest1(XXX);
  length tmp $20;
  tmp='&XXX';
  res=resolve(tmp);
  put res=;
%Mend;
data _null_;
  %Mtest1(abc);
run;

%Macro Mtest2(XXX);
data _null_;
  length tmp $20;
  tmp='&XXX';
  res=resolve(tmp);
  put res=;
run;
%Mend;
%Mtest2(abc); 

一見、マクロ化しているのがdataステップ自体なのか、dataステップ内のステートメントなのかの違いに見えますが、結果に違いが出てきます。

%Mtest1の方では、WARNING: XXXのシンボリック参照を解決できません。という警告が出てしまっています。




マクロパラメータと変数値を結合してマクロ変数にするようなケースでちょっと面倒が発生しそうですが、dataステップ内で展開するマクロ参照を使用する場合には、&で展開してあげれば対応できると思います。

%let name_1_x=mike;

data sample;
  key='x';output;
run;

%Macro Mgetname(id);
  res='&&name_&id._'||key;   * <-これはNG;
  res='&name_'||"&id._"||key;* <-マクロを展開しておくことで、resolveにパラメータのマクロ変数を処理させない;
  name=resolve(res);
%Mend;

data _null_;
  set sample;
  length name $10;
  %Mgetname(1);
  put name=;
run;
resolve関数は、dataステップ内で展開したマクロ参照のパラメータ値を取得できない。

なんででしょうか?


ちなみに、symgetは以下のような使い方ができます

%let name_1=mike;
%let name_2=alice;

data sample;
  key='x';output;output;
run;

%Macro Mgetname(id);
  idx=symget("&id");
  res='&name_'||idx;
  name=resolve(res);
%Mend;

data _null_;
  set sample;
  length name $10;
  call symputx('index',_n_);
  %Mgetname(index);
  put name=;
run;

obs番号である_n_をマクロパラメータとして指定したい場合、そのまま_n_とすると、文字列の「_n_」として処理されてしまうため、一旦call symputxでマクロ変数に格納し、それをマクロ内でsymgetを使って取得、というやり方です。