この記事は「Common Lispと人工知能プログラミング」からの抜粋です.
エクステントとは何か?
ここで理解が難しい変数のエクステントという概念について説明してみよう.
誰も触れない秘密の変数
let 変数の特殊な使い方として,システム中にあって直接にはだれにもさわれない,保守用関数を通してでないとアクセスできない変数を容易に作ることができる.次のコードを見られたい.
cl-user(1): (let (x)
(defun my-confidential-key-get () x)
(defun my-confidential-key-set (val) (setq x val)))
my-confidential-key-set
cl-user(2): (my-confidential-key-set 123)
123
cl-user(3): (my-confidential-key-get)
123
cl-user(4): (setq x 0)
0
cl-user(5): (my-confidential-key-get)
123
cl-user(6): (my-confidential-key-set 0)
0
cl-user(7): (my-confidential-key-get)
0
このコードで用いられた let 変数 x はシステム中のどこかにその場所が確保されているわけだが,いったん let 構文を完成させてしまうと,もうどこからもその変数に直接はアクセスすることはできず,このコードでは my-confidential-key-get と my-confidential-key-set を経由するしかアクセスする方法がない.昔 Lisp にオブジェクト指向プログラミングの機能がないころには,このようなコードで変数隠ぺいを行って,オブジェクト指向っぽいプログラムを書くというようなことが行われた.
さてこの「誰にも触れない変数」も何か不思議に思われなかっただろうか.変数 x は静的スコープだから外側からは触れないというのは分かる.でも my-confidential-key-set でセットした値が,その関数を終えたあとでも残っている.そしてその値を取り出すことも,セットしなおすこともできる.つまり,スコープの外にあっても実はその存在は「生きている」.スコープとは変数の参照つまり見える見えないに関する概念だが,これとは別に,変数の値の確立が「生きている生きていない」の概念がある.それがエクステントである.Common Lisp の変数は,変数の参照と値の確立が分離している.ラムダ変数や let 変数は特殊変数の宣言がないかぎり静的変数であり,変数への参照の範囲すなわちスコープはその構文の字面上で確立された範囲であるが,「参照の可能性が存在し続ける限り束縛が存在し続けるので,時間的な制約はない」(CLtL2, 5.1.2).そういうものを無限エクステント(indefinite extent)と呼ぶ.「let 変数の束縛はレキシカルスコープと無限エクステントを持つ」(CLtL2, 7.5, let Special Form).スペシャル宣言のないラムダ変数も同様である(CLtL2, 第3章).一方,スペシャル宣言されたラムダ変数や let 変数(特殊変数または動的変数)では,その値の確立は構文要素が実行完了した時に解除される.これを動的エクステントと呼ぶ.もちろん束縛が解除されれば,参照には意味がないから,特殊変数では変数の参照と値の確立は一致していると言ってもよいが,ややこしいことに,動的変数はその値は動的ではあるが,参照は束縛が有効であるかぎりどこからでもその値を見ることができる.これを無限スコープの動的エクステントと呼ぶ.動的スコープとはこの無限スコープの動的エクステントのことである.上記の議論は同じ名前の変数による値の隠ぺいとは別の話であり,実際にはこれに静的でも動的でも隠ぺいの議論が加わる.
cl-user(17): (defun foo0 ()
(let ((x 0))
(declare (special x))
(foo1)))
foo0
cl-user(18): (defun foo1 ()
(declare (special x))
(print x)
(let ((x 1))
(declare (special x))
(foo2)))
foo1
cl-user(19): (defun foo2 ()
(declare (special x))
(print x)
(let ((x 2))
(declare (special x))
(foo3)))
foo2
cl-user(20): (defun foo3 ()
(declare (special x))
(print x)
(let ((x 3))
(declare (special x))
(foo4)))
foo3
cl-user(21): (defun foo4 ()
(declare (special x))
(print x))
foo4
cl-user(22): (foo0)
0
1
2
3
3
この例では変数 x はすべてスペシャルである.関数実行に入るたびに,let 変数であるが特殊である変数 x の値がスタックに積まれていくが,それぞれの関数の冒頭で無限スコープによる参照によって関数実行時にそれ以前に積まれた値を参照して印刷している.
トップレベルで変数に値を与えるには,setq(あるいは set か setf )するしかないが,関数の内部であっても自由変数への setq はその変数がラムダ変数や let 変数以外の変数ならばトップレベルにおける変数の扱いと同様に無限スコープとなって,特殊変数と区別がつかなくなってしまう.自由変数の使用は思わぬバグを潜ませることにもなるので,無限スコープの変数については明示的にグローバル宣言することが望ましい.