npt-japanese

% Declaration DYNAMIC-EXTENT

UP


Declaration DYNAMIC-EXTENT

Declaration DYNAMIC-EXTENT

構文

(dynamic-extent [[var* | (function fn)*]])

引数

var - 変数名
fn - 関数名

有効な文脈

declare

影響する束縛の型

変数、関数

定義

この宣言で指定された各変数variが どのような扱いになるのかを説明します。 あるフォームに含まれる部分をFとします。 Fによって対象の変数が束縛されている必要はありませんが、 あとでそれぞれの場合を考えます。 変数variが取る値をvijとします。 vijがある時点でvariの値になるときに、 xijkvijの「他からアクセス不可の部分」であるとします。 ちょうどFの実行が終了した直後に、 もしFvariを束縛していた場合は、 xijkはアクセス不可能です。 もしFvariを束縛していない場合は、 xijkvariの現在の値の「他からアクセス不可の部分」です。 同じ関係が関数の名前空間の束縛について各fniにも生じます。

コンパイラーはこの情報を処理系に適切な方法で、 かつCommon Lispの意味に衝突しないような方法で 使うことが許されます。

dynamic-extent宣言は、自由宣言と境界宣言のどちらでも利用できます。

dynamic-extent宣言にあるvarfnの名前は、 シンボルマクロとマクロの束縛を参照してはいけません。

例文

初期値をスタック領域で確保するには、 そのオブジェクトの作成時にスタック領域の確保が可能かどうかを 知る必要があるため、 レキシカルに初期値が現れてない変数に対して 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, c2nilは、 xyの他からアクセス不可の部分であることに注意してください。 なぜなら、それらは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)

参考

declare

備考

この最も一般的な最適化は、var変数の初期値のオブジェクトを スタック領域に確保します。

実装は単純にこの宣言を無視することも許されます。


TOP, Github