静的スコープ
静的スコープ(せいてきスコープ、英: static scope)とは、プログラミング言語におけるスコープの一種。字句のみから決定できるため、字句スコープまたはレキシカルスコープ (lexical scope) ともいう[1]。
概要
まず、一般的なローカル変数のスコープについて考える。
ブロックなどの構造を持つプログラミング言語では、あるブロックの内側のローカル変数は、そのブロックの外側からは「見えない」というものが多い。ただし、以前のJavaScript (ECMAScript) のように、サポートされるのは関数内ローカルのみで、ブロックローカルというスコープは無いものもある[2]。
疑似コードによる例を挙げる。
A { var x; } B { var x; // A内のxとは別物 var f; C { var y; // Cの内側からしか見えない y = rand(); f = function(z) { return y + z; }; } x = f(42); }
ブロックA
で定義されている変数x
とブロックB
で定義されている変数x
は同じ識別子を持つが、ブロックが異なるため実体は別である。また、ブロックB
からは、さらに内側のブロックC
で定義されている変数を参照することはできない。逆にブロックC
からはブロックB
で定義されている変数x
とブロックC
で定義されている変数y
が参照可能である。
以上のようなスコープはローカル変数として一般的なものである。しかし、上記の疑似コード中にある f = function(z) { return y + z; };
のように、スコープ内にある手続きオブジェクト(クロージャ)によって、そのスコープ内における束縛を外部に持ち出すといったような(ことが可能な言語の)場合に、「静的スコープか否か」といったようなことが議論になる。
次の節で述べるCommon Lispでのdefvar
や、Perlにおいてmy
ではなくlocal
で宣言した変数といった動的スコープの場合は、その名前解決が、そのソースコードにおいて見えるように解決される(静的スコープ)のではなく、実行時の関数呼び出しの経路(コールスタック)から、ひとつひとつ呼出元をたどるようにして行われる(詳細は「動的スコープ」の記事を参照)。
それに対し静的スコープの場合は、手続きオブジェクトがクロージャによって実装されているなどのようにして、そのスコープにおける束縛が手続きオブジェクトに「ひっ付いて」おり、コールスタックによってではなく、手続きオブジェクトが作られた場所のスコープで名前が解決される(「コールスタック」の記事中の「ルーチンの入れ子における静的スコープサポート」という記述も参照)。
Common Lispにおける例
Common Lispは、静的スコープを一般的なルールとし、動的スコープの名前については明示が必要である。
(defvar *a*) ;; *a* を動的スコープで値なしで宣言する。 ;; アスタリスクは名前の一部である。 ;; defvarは、以降のそれに対する束縛が静的なものでなく、 ;; 動的なものである事を保証する。 (setf *a* 5) ;; 変数 *a* を整数 5 に設定する。 (let ((*a* 3)) *a*) ; --> 3 ;; 明示的にletの中で上書きされた場合、*a*は3 *a* ; --> 5 ;; letの外に出るともとに戻る (defvar func-lex) (setf func-lex (let ((a 3)) (lambda () a))) ;; 現在、静的スコープ内の3がlambdaの中に残っている ;; したがって、 (let ((a 5)) (funcall func-lex)) ; --> 3 ;; 外からaを書き換えて呼び出しても、 ;; 保存された静的な束縛 a = 3 がまだ残っており、 ;; それが有効になって答えは3となる ;; 一方、*a*について考えると、 (defvar func-dyn) (setf func-dyn (let ((*a* 3)) (lambda () *a*))) (funcall func-dyn) ; --> 5 ;; *a*は動的スコープの変数として宣言されているため、 ;; lambdaの中の *a* は常に ;; その時点での最も外側の変数を指すことになる。 ;; 静的スコープに束縛された *a* = 3 はlambdaの中に保存されない
脚注
関連項目
- スコープ (プログラミング)
- 動的スコープ