健全なマクロ

出典: フリー百科事典『ウィキペディア(Wikipedia)』

これはこのページの過去の版です。Semi-Brace (会話 | 投稿記録) による 2020年10月2日 (金) 16:40個人設定で未設定ならUTC)時点の版 (bulk replace (ns=0, <source> → <syntaxhighlight>) via script)であり、現在の版とは大きく異なる場合があります。

健全なマクロHygienic macros)とは、識別子が誤って捕捉されてしまう問題が起こらないことが保証されているマクロである。Scheme[1]Dylanなどのプログラミング言語はこの機能を持っている[2]。識別子の誤捕捉という問題は、健全なマクロが導入される以前からLispコミュニティで広く知られていた。マクロの作者は、ユニークな識別子を生成するgensymなどの言語機能を使ったり、通常使用されないような複雑な識別子を用いることで、この問題を回避してきた。健全なマクロは、誤捕捉の問題を解決する方法をマクロの展開機能に組み込むことで、問題をプログラマティックに解決している。「健全」を意味する"hygiene"という語は、Kohlbeckerらが1986年に書いた健全なマクロの展開を導入した論文で、数学の用語の影響を受けて初めて使用された[3]

マクロの問題

健全なマクロの機能を持たないプログラミング言語では、マクロの展開中に作成された変数の束縛によって、すでに存在する変数の束縛が隠されてしまう可能性がある。C言語では、次のようなコードによってこの問題を説明できる。

#define INCI(i) {int a=0; ++i;}
int main(void)
{
    int a = 0, b = 0;
    INCI(a);
    INCI(b);
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

C言語のプリプロセッサで上のコードを変換すると、次のコードが生成される。

int main(void)
{
    int a = 0, b = 0;
    {int a=0; ++a;};
    {int a=0; ++b;};
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

トップのスコープで宣言された変数 a は、マクロの中にある変数 a によって隠されてしまっている。その結果、プログラムを実行しても変数の値は変化せず、コンパイルされたプログラムは次のように出力する。

a is now 0, b is now 1

この問題を解決する最も簡単な方法は、現在のプログラムに含まれるどんな変数とも衝突しない名前を、マクロ変数に与えることである。

#define INCI(i) {int INCIa=0; ++i;}
int main(void)
{
    int a = 0, b = 0;
    INCI(a);
    INCI(b);
    printf("a is now %d, b is now %d\n", a, b);
    return 0;
}

INCIa という名前の変数が作られないかぎり、この解決方法によって正しい出力が得られる。

a is now 1, b is now 1

現在のプログラムでは問題が解決されたが、この解決策は十分頑強であるとはいえない。プログラマは、マクロの内部で使用された変数と、残りのプログラムで使われる変数とを、常に衝突しないように気をつけなければならない。たとえば、変数 INCIa についてマクロ INCI を使おうとすれば、以前のマクロで変数 a について問題が起きたのと同じように問題が起こることになる。

「健全な問題」は、変数の束縛に拡張を加える。次のようなCommon Lispのマクロを考えてみる。

 (defmacro my-unless (condition &body body)
  `(if (not ,condition)
     (progn
       ,@body)))

このマクロには、変数への参照が含まれていないので、シンボル"if"、"not"、"progn" は、すべて普通の定義に束縛される。しかし、上のマクロが次のように使用された場合、

 (flet ((not (x) x))
   (my-unless t
     (format t "This should not be printed!")))

"not"の定義はローカルな定義に置き換えられ、my-unless の展開結果が変化する。(標準の関数やオペレータのグローバルまたはローカルな再定義は、ANSI

 Common Lispによれば、不確定な振る舞いを起こすとされている。そのため、上のような使い方をしても、実際には実装によってエラーと判断される可能性がある。)

一方、健全なマクロは、すべての識別子("if"や"not")のレキシカルスコープが自動的に保存される。この性質は、「参照透過性」と呼ばれている。

しかし、プログラム内で定義した関数は同じようには保護されていないので、次のようなコードでは問題が起こることがある。

 (defmacro my-unless (condition &body body)
  `(if (user-defined-operator ,condition)
     (progn
       ,@body)))

 (flet ((user-defined-operator (x) x))
   (my-unless t
     (format t "This should not be printed!")))

この問題のCommon Lispでの解決策は、パッケージを利用することである。 パッケージの中に置かれた my-unless マクロでは、user-defined-operator はそのパッケージ内のプライベートなシンボルである。そして、ユーザーコード内の user-defined-operator は異なるシンボルとなり、マクロ内のシンボルとは無関係なものになる。

しかし、健全なマクロを使用しているSchemeなどの言語は、マクロ展開の処理の一部として自動的に参照透過性を保証することで、識別子の誤捕捉を防いでいる。ただし、識別子を意図的に補足するために、健全なマクロのメカニズムを明示的に無視できるようにしている実装もある。

たとえば、次のSchemeによるmy-unless の実装は、期待通りの振る舞いをする。

 (define-syntax my-unless
   (syntax-rules ()
     [(_ condition body)
      (if (not condition)
          body
          (void))]))

  (let ([not (lambda (x) x)])
    (my-unless #t
      (displayln "This should not be printed!")))

脚注

  1. ^ Richard Kelsey; William Clinger; Jonathan Rees et al. (August 1998). “Revised5 Report on the Algorithmic Language Scheme”. Higher-Order and Symbolic Computation 11 (1): 7–105. doi:10.1023/A:1010051815785. http://www.schemers.org/Documents/Standards/R5RS/. 
  2. ^ Richard Kelsey; William Clinger; Jonathan Rees et al. (August 1998). “Revised5 Report on the Algorithmic Language Scheme”. Higher-Order and Symbolic Computation 11 (1): 7–105. doi:10.1023/A:1010051815785. http://www.schemers.org/Documents/Standards/R5RS/. 
  3. ^ Richard Kelsey; William Clinger; Jonathan Rees et al. (August 1998). “Revised5 Report on the Algorithmic Language Scheme”. Higher-Order and Symbolic Computation 11 (1): 7–105. doi:10.1023/A:1010051815785. http://www.schemers.org/Documents/Standards/R5RS/. 

参考文献