クロージャってなんじゃろ?

今日もまたSASマクロの悪口、Proc Lua導入のメリットを紹介する記事です。

今回はクロージャです。

ちなみにクロージャやコルーチン(いずれ紹介)などスコープ絡みの部分が、Luaのメリットのひとつであるということは、かなり初期の頃からfukiyaさんやmatsuさんが示唆されてました。僕はその時点では全く意味がわかってませんでした。本当尊敬です。

wikipediaでクロージャと調べると
「引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる」
う~ん、やっぱり、なんじゃろ??

closure → 直訳すると閉鎖、閉店ガラガラみたいな感じ クローズ。

この閉じてるというのがクロージャのポイントなんですね。

SASマクロで何か処理をして、その結果を保持して、次にそのマクロを実行するときに参照して処理したいとなったらどうします?

例えば、データセットをパラメータとして与えると、オブザベーション数をカウントして返す。呼び出されるたびにそれまでにカウントした合計に足していくマクロを作れといわれたらどうしますか?

以下のようにかけると思います。

%let tobs=0;

/*マクロ定義部分*/
%macro m(ds);
data _null_;
 call symputx('tobs',nobs+&tobs);
 if 0 then set &ds nobs=nobs;
 stop;
run;
%put 合計=  &tobs;
%mend;

/*実行部分*/
%m(sashelp.class)
%m(sashelp.iris)
%m(sashelp.cars)

結果は







何がいいたいかと言いますと、値の保持、受け渡しの手段としてグローバルマクロ変数
(&tobs)を作成して使ってますね。

SASの場合、データステップ内であればretainを使って値を保持できますが、ステップをまたぐ場合、データセットを作ってその中に入れるか、あるいはグローバルマクロに入れるかしか方法がありません。
(まあ、フォーマット作るとか外部ファイルにだすとかもあるかもしれませんが…)

SAS使いにとっては疑いのない当たり前の手段ですが、これは本当はちょっと怖いことやってるんですね。

値を受け渡す前に、意図せずマクロ変数の中身が変えられていたら?参照するデータセットが意図せず更新されたら?
マクロを呼び出すときに、参照するものが、本当に前回までの結果なのか保証できますか?

例えばプログラムをどんどん増築したり、複数のプログラムをincludeしまくる等した時、プログラム作成者が複数人になればグローバルマクロ変数の名前やデータセット名がかぶる事は珍しいことではないです。

値がオープンで、処理と結びついていないところで参照・更新できうるということが問題なわけです。

なんとなく話が見えてきたかと思いますが、それに対してのクロージャはというと

/*関数定義部分*/
proc lua;
 submit;
  function L(ds)
   local nobs= 0
   return function(ds)
    local dsid = sas.open(ds)
    nobs = nobs+sas.nobs(dsid)
    sas.close(dsid)
    return nobs
   end
  end
endsubmit;
quit;

/*実行部分*/
proc lua;
 submit;
  L1=L()
  print ("合計=",L1("sashelp.class"))
  print ("合計=",L1("sashelp.iris"))
  print ("合計=",L1("sashelp.cars"))
 endsubmit;
quit;

結果は同じ






ですが

proc lua;
 submit;
  print(nobs)
 endsubmit;
quit;

としても、




中身は空、つまり定義されていない。
nobsという変数は、Lの中でのみ存在・保持されていて、外からは触れられないんですね。

これにより、他の処理でnobsという変数名を使っても衝突することがなくなります。
完全に閉じてるわけです。

Lを代入して変数であり関数のL1(インスタンスみたいなもん)を作って実行しているわけですが、例えばL2 L3を作ってそれぞれ
独立してカウントすることもできます

proc lua;
submit;
 L2=L()
 L3=L()
 print ("合計=",L2("sashelp.class"))
 print ("合計=",L2("sashelp.iris"))
 print ("合計=",L3("sashelp.class"))
 print ("合計=",L2("sashelp.class"))
 print ("合計=",L3("sashelp.class"))
endsubmit;
quit;









さて、肝心の解説(たぶん正確ではないから詳しい人に聞いてね)

  function L(ds)
   local nobs= 0
   return function(ds)
    local dsid = sas.open(ds)
    nobs = nobs+sas.nobs(dsid)
    sas.close(dsid)
    return nobs
   end
  end
L1=L()

関数Lはそのローカルの範囲に変数nobsと戻り値として名前をもたない関数(return function(ds)の部分ね。無名関数という)をもってますよね。、
このとき戻り値(関数)には自分の上層にいるL(エンクロージャという)のローカル変数nobsも環境としてくっついているんですね。
しかし、L1に代入されたとき、戻り値の関数から見た変数nobsは、ローカル変数ではなく・グローバル変数でもないという謎の状態になります
ローカル変数ではないので、関数終了後に消えるという運命は適応されません。、かといってグローバルではないので他からアクセスもできません
関数実行時にのみ内側でしかアクセスできずに残る。このようにグローバルやローカルではなく、処理ブロック自体がスコープを閉じ込めているのをレキシカルスコープ(静的スコープ)といい、今回のnobsは「レキシカル変数」といわれるのです

0 件のコメント:

コメントを投稿