% Declaration DYNAMIC-EXTENT
Declaration DYNAMIC-EXTENT
(dynamic-extent
[[var* |
(function
fn)*]])
var - 変数名
fn - 関数名
declare
変数、関数
この宣言で指定された各変数vari
が
どのような扱いになるのかを説明します。
あるフォームに含まれる部分をF
とします。
F
によって対象の変数が束縛されている必要はありませんが、
あとでそれぞれの場合を考えます。
変数vari
が取る値をvij
とします。
vij
がある時点でvari
の値になるときに、
xijk
をvij
の「他からアクセス不可の部分」であるとします。
ちょうどF
の実行が終了した直後に、
もしF
がvari
を束縛していた場合は、
xijk
はアクセス不可能です。
もしF
がvari
を束縛していない場合は、
xijk
はvari
の現在の値の「他からアクセス不可の部分」です。
同じ関係が関数の名前空間の束縛について各fni
にも生じます。
コンパイラーはこの情報を処理系に適切な方法で、 かつCommon Lispの意味に衝突しないような方法で 使うことが許されます。
dynamic-extent
宣言は、自由宣言と境界宣言のどちらでも利用できます。
dynamic-extent
宣言にあるvarとfnの名前は、
シンボルマクロとマクロの束縛を参照してはいけません。
初期値をスタック領域で確保するには、
そのオブジェクトの作成時にスタック領域の確保が可能かどうかを
知る必要があるため、
レキシカルに初期値が現れてない変数に対して
dynamic-extent
宣言をするのは一般的に有効ではありません。
例えば、次のように記載します。
(defun f ()
(let ((x (list 1 2 3)))
(declare (dynamic-extent x))
...))
これはコンパイラーに対して、
ローカル変数x
によって保持されるリストを
スタック領域から確保して欲しいと許可を出しています。
これは許可されますが、実際にはおそらく使いやすくありません。
次の例を見てみます。
(defun g (x) (declare (dynamic-extent x)) ...)
(defun f () (g (list 1 2 3)))
多くのコンパイラーはおそらくf
の中では、
g
の引数をスタック領域に確保しません。
なぜならコンパイラーがf
内部からg
の状況を推定する際に
モジュール性の違反であるとみなすからです。
g
の定義が互換性のないものに変更されたときに、
f
の再コンパイルに責任を持てる実装のみが、
f
内のg
へのリスト引数を
正当な方法としてスタック確保できます。
別の例を示します。
(declaim (inline g))
(defun g (x) (declare (dynamic-extent x)) ...)
(defun f () (g (list 1 2 3)))
(defun f ()
(flet ((g (x) (declare (dynamic-extent x)) ...))
(g (list 1 2 3))))
この例では、いくつかのコンパイラーは最適化を可能にしますし、 そうでないものもあります。
別の最適化で「スタックに確保されたrest
リスト」と呼ばれるものは
(実装がこの最適化をサポートしているならば)、
次のように行います。
(defun f (&rest x)
(declare (dynamic-extent x))
...)
この例ではx
の初期値は明になっていないことに注意してください。
このような場合でも関数f
は引数として渡されるリストx
の編成に責任を持ちます。
もし実装が最適化をサポートしているのであれば、
コンパイラーは関数f
に対して、リストの生成をヒープ領域ではなく
スタック領域に構築するような最適化を行います。
下記の例を見てみます。
(let ((x (list 'a1 'b1 'c1))
(y (cons 'a2 (cons 'b2 (cons 'c2 nil)))))
(declare (dynamic-extent x y))
...)
x
の他からアクセス不可の部分は3つのコンスであり、
y
の他からアクセス不可の部分は他の3つのコンスです。
シンボルa1
, b1
, c1
, a2
, b2
, c2
とnil
は、
x
かy
の他からアクセス不可の部分であることに注意してください。
なぜなら、それらはintern
されており、
intern
されているものはパッケージからアクセスできるからです。
しかし、もしintern
されていない新しいシンボルが使われている場合、
それは、そのシンボルが含まれるリストの
他からアクセス不可の部分になります。
;; この例では、Xに束縛されているリストをスタック領域に
;; 確保することを実装に許可しています。
(let ((x (list 1 2 3)))
(declare (dynamic-extent x))
(print x)
:done)
>> (1 2 3)
=> :DONE
;; この例では、Lに束縛されるリストがスタック確保可能です。
(defun zap (x y z)
(do ((l (list x y z) (cdr l)))
((null l))
(declare (dynamic-extent l))
(prin1 (car l)))) => ZAP
(zap 1 2 3)
>> 123
=> NIL
;; いくつかの実装は、Lに束縛されるリストをスタックに確保できるよう
;; LIST-ALL-PACKAGESのコードを開放しているかもしれません。
(do ((l (list-all-packages) (cdr l)))
((null l))
(declare (dynamic-extent l))
(let ((name (package-name (car l))))
(when (string-search "COMMON-LISP" name) (print name))))
>> "COMMON-LISP"
>> "COMMON-LISP-USER"
=> NIL
;; いくつかの実装では、restのリストをスタックに確保できます。
;; 下記のような宣言は、そのような実装に対してrestリストを
;; スタック確保したいという合図になるはずです。
(defun add (&rest x)
(declare (dynamic-extent x))
(apply #'+ x)) => ADD
(add 1 2 3) => 6
(defun zap (n m)
;; (RANDOM (+ M 1))を計算するには、だいたいO(N)の速度になります。
;; これはとても遅いですが良いコンパイラーなら少なくとも
;; 大量のヒープ領域を消費することはないでしょう。 :-}
(let ((a (make-array n)))
(declare (dynamic-extent a))
(dotimes (i n)
(declare (dynamic-extent i))
(setf (aref a i) (random (+ i 1))))
(aref a m))) => ZAP
(< (zap 5 3) 3) => true
下記の例は値x
がその範囲から外で使用されているためエラーです。
(length (list (let ((x (list 1 2 3))) ; Invalid
(declare (dynamic-extent x))
x)))
(progn (let ((x (list 1 2 3))) ; Invalid
(declare (dynamic-extent x))
x)
nil)
この最も一般的な最適化は、var変数の初期値のオブジェクトを スタック領域に確保します。
実装は単純にこの宣言を無視することも許されます。