Macro DEFSTRUCT

UP


Macro DEFSTRUCT

Macro DEFSTRUCT

構文

defstruct name-and-options [documentation] {slot-description}*
=> structure-name

name-and-options::= structure-name | (structure-name [[options]]) 
options::= conc-name-option | 
           {constructor-option}* | 
           copier-option | 
           include-option | 
           initial-offset-option | 
           named-option | 
           predicate-option | 
           printer-option | 
           type-option 
conc-name-option::= :conc-name | (:conc-name) | (:conc-name conc-name) 
constructor-option::= :constructor | 
                      (:constructor) | 
                      (:constructor constructor-name) | 
                      (:constructor constructor-name constructor-arglist) 
copier-option::= :copier | (:copier) | (:copier copier-name) 
predicate-option::= :predicate | (:predicate) | (:predicate predicate-name) 
include-option::= (:include included-structure-name {slot-description}*) 
printer-option::= print-object-option | print-function-option 
print-object-option::= (:print-object printer-name) | (:print-object) 
print-function-option::= (:print-function printer-name) | (:print-function) 
type-option::= (:type type) 
named-option::= :named 
initial-offset-option::= (:initial-offset initial-offset) 
slot-description::= slot-name |  
                    (slot-name [slot-initform [[slot-option]]]) 
slot-option::= :type slot-type |  
               :read-only slot-read-only-p 

引数と戻り値

conc-name - 文字列指定子
constructor-arglist - boaラムダリスト
constructor-name - シンボル
copier-name - シンボル
included-structure-name - すでに定義されている構造体の名前。 派生された型は許されず、 構造体名に展開されるものも禁止されることに注意して下さい。
initial-offset - 非負の整数
predicate-name - シンボル
printer-name - 関数名かラムダ式
slot-name - シンボル
slot-initform - フォーム
slot-read-only-p - generalized-boolean
structure-name - シンボル
type - 次の型指定子のどれかであり、 list, vector, (vector size), あるいはその他の実装で適用可能な定義された型指定子。
documentation - 文字列。評価されません。

定義

defstructは構造体の型を定義し、 その名前はstructure-typeであり、 slot-optionによって指定された名前付きのスロットが付きます。

defstructは、 スロットを読み込むリーダーと、 そのようなリーダーをsetf上で正しく動作する 修正する機能を定義します。 無効にしない限り、 name-pという名前の述部と、 make-constructor-nameという名前の生成関数、 copy-constructor-nameというコピーの関数が定義されます。 これらの全ての名前の関数は自動的に生成され、 自動的にinlineとして宣言されます (実装が区別できる場合)。

documentationが与えられると、 structure-nameがドキュメント文字に与えられ、 種別structureと共に割り当てられます。 また:typeが指定されていないときは、 structure-nameがドキュメント文字、 種別がtypeという、 クラス名がstructure-nameの クラスオブジェクトのための割り当てが行われます。

defstructは、 本機能によって作成された構造体のインスタンスを生成するために使われる 構築関数を定義します。 標準の名前はmake-structure-nameです。 引数のconstructorオプションによって与えられた名前によって、 違う名前を付けることができます。 nilが指定されたときは、 構築関数は作成されません。

新しい構造体の型が定義された後は、 その型のインスタンスは通常はその型の構築関数を使うことで作成できます。 構築関数の呼び出しは、次のようなフォームになります。

(constructor-function-name
 slot-keyword-1 form-1
 slot-keyword-2 form-2
 ...)

構築関数の引数は、全てキーワード引数です。 各キーワード引数のスロットは、 構造体のスロットの名前に対応したキーワードの 名前でなければいけません。 全てのkeywordformは評価されます。 もしスロットがこの方法で初期化されないときは、 それは構築関数呼び出し時にスロットの定義内の slot-initformを評価することによって初期化されます。 もしslot-initformが与えられなかったとき、 何かの値が明に代入される前に そのスロットを読み込もうとしたときの結果は未定義です。

defstructで指定された各slot-initformの要素は、 構築関数が指定されてない要素以外にに対して使われたとき、 slot-initformは構築関数を呼び出すたびに再評価されます。 slot-initformは、 特定の構造体インスタンスの生成に必要になるまで 評価されません。 もし決して必要としないのであれば、 スロット定義の型を指定したとしても、 決して型の不適合によるエラーは発生しません。 この場合、警告は発生されません。 例えば、下記の一連のフォームは、最後の呼び出しでのみエラーが生じます。

(defstruct person (name 007 :type string)) 
(make-person :name "James")
(make-person)

これは構築関数のキーワードパラメーターが slot-initformの初期化フォームとして使用されたかのように動作します。

スロットの名前のシンボルは、実装において 構築巻子のラムダ式の変数にその名前を使用してはいけません。 なぜならそれらのシンボルは、specialに宣言されたり、 その名前で定数変数が定義されるかもしれないからです。 スロットのデフォルトの初期化フォームは、 defstructフォーム自身が現れた レキシカルな環境内で評価されます。 あるいは構築関数の呼び出しが現れた 動的な環境で評価されます。

例えば、もし(gensym)フォームが初期化フォームとして 構築関数の呼び出し内か、 あるいはdefstructによるデフォルト初期化フォーム内かの どちらかで使われたとき、 全ての構築関数の呼び出しにおいて、 gensymが一度だけ呼ばれて 新しいシンボルを生成します。

defstructの各slot-descriptionは、 ゼロかあるいは複数のslot-optionを指定できます。 slot-optionはキーワードと値 (評価されるフォームではなく値はそれ自身) のペアを含みます。 例えば、

(defstruct ship
  (x-position 0.0 :type short-float)
  (y-position 0.0 :type short-float)
  (x-velocity 0.0 :type short-float)
  (y-velocity 0.0 :type short-float)
  (mass *default-ship-mass* :type short-float :read-only t))

これは、各スロットが常にshort-floatを含み、 最後のスロットは一度shipを構築したら 変更不可能なスロットとして定義されています。

利用可能なslot-optionは下記の通り。

下記のキーワードオプションは、defstructで使うことができます。 defstructオプションは、 キーワードか、キーワードとそのキーワードの引数のリストの どちらかを指定できます。 キーワード自身が指定されたときは、 そのキーワードと引数なしのリストを指定したことと同じです。 defstructオプションの構文は、 slot-optionが使われたペアの構文と違っています。 これらのオプションどのどの部分も評価されません。

 (defstruct (door (:conc-name dr-)) knob-color width material) =>  DOOR
 (setq my-door (make-door :knob-color 'red :width 5.0)) 
=>  #S(DOOR :KNOB-COLOR RED :WIDTH 5.0 :MATERIAL NIL)
 (dr-width my-door) =>  5.0
 (setf (dr-width my-door) 43.7) =>  43.7
 (dr-width my-door) =>  43.7
(defstruct person name age sex)
(defstruct (astronaut (:include person)
                      (:conc-name astro-))
   helmet-size
   (favorite-beverage 'tang))
(setq x (make-astronaut :name 'buzz
                        :age 45.
                        :sex t
                        :helmet-size 17.5))
(person-name x) =>  BUZZ
(astro-name x) =>  BUZZ
(astro-favorite-beverage x) =>  TANG
(reduce #'+ astros :key #'person-age) ; 空かもしれないastrosシーケンスの
                                      ; 年齢の合計を得ます
 (typep (make-astronaut) 'person) =>  true
(:include included-structure-name slot-description*)
 (defstruct (astronaut (:include person (age 45)))
    helmet-size
    (favorite-beverage 'tang))
(defstruct (binop (:type list) :named (:initial-offset 2))
  (operator '? :type symbol)   
  operand-1
  operand-2) =>  BINOP
(defstruct (annotated-binop (:type list)
                            (:initial-offset 3)
                            (:include binop))
 commutative associative identity) =>  ANNOTATED-BINOP
(make-annotated-binop :operator '*
                      :operand-1 'x
                      :operand-2 5
                      :commutative t
                      :associative t
                      :identity 1)
  =>  (NIL NIL BINOP * X 5 NIL NIL NIL T T 1)
(defstruct (binop (:type list) (:initial-offset 2))
  (operator '? :type symbol)
  operand-1
  operand-2) =>  BINOP
(make-binop :operator '+ :operand-1 'x :operand-2 5)
=>  (NIL NIL + X 5)
(make-binop :operand-2 4 :operator '*)
=>  (NIL NIL * NIL 4)
(defstruct (binop (:type list) :named (:initial-offset 2))
  (operator '? :type symbol)
  operand-1
  operand-2) =>  BINOP
(make-binop :operator '+ :operand-1 'x :operand-2 5) =>  (NIL NIL BINOP + X 5)
(make-binop :operand-2 4 :operator '*) =>  (NIL NIL BINOP * NIL 4)
(defstruct (binop (:type list))
  (operator '? :type symbol)
  operand-1
  operand-2) =>  BINOP
(make-binop :operator '+ :operand-1 'x :operand-2 5) =>  (+ X 5)  
(make-binop :operand-2 4 :operator '*) =>  (* NIL 4)
(defstruct (binop (:type list) :named)
  (operator '? :type symbol)
  operand-1
  operand-2) =>  BINOP
(make-binop :operator '+ :operand-1 'x :operand-2 5) =>  (BINOP + X 5)
(make-binop :operand-2 4 :operator '*) =>  (BINOP * NIL 4)
(defun binop-p (x)
  (and (consp x) (eq (car x) 'binop))) =>  BINOP-P
(defmethod print-object ((object structure-name) stream)
  (funcall (function printer-name) object stream <<current-print-depth>>))
(defmethod print-object ((object structure-name) stream)
  (funcall (function printer-name) object stream))
(defstruct (quux (:type list) :named) x y)
(deftype quux () '(satisfies quux-p))
(typep (make-quux) 'quux)
(typep (list 'quux nil nil) 'quux)

defstructの構造体を再定義したときの結果は未定義です。

defstructに何もオプションを指定しなかった場合は、 新しい構造体のインスタンスでは 次のような操作を行う関数が自動的に定義されます。

もしdefstructフォームがトップレベルフォームとして現れたとき、 コンパイラーは構造体の型を後続の宣言(例えばdeftype)で 有効な型として認識できるようにし、構造体のスロットのリーダーを setfで使用可能にしなければなりません。 さらにコンパイラーは同じファイルで構造体名を参照する 別のdefstruct定義で:includeを使用できるように 構造体の型についての十分な情報を保存しなければなりません。 defstructが生成する関数はコンパイル時の環境で 定義する必要はありませんが、 コンパイラーは後続のinline呼び出しのために、 関数のコードに関する十分な情報を保存する必要があるかもしれません。 #Sリーダーマクロは、コンパイル時に新しく定義された構造体型については 認識するかもしれませんししないかもしれません。

例文

構造体の定義の例を下記に示します。

(defstruct ship
  x-position
  y-position
  x-velocity
  y-velocity
  mass)

これは全てのshipが5つの名前を持った要素持つ オブジェクトであると宣言しています。 このフォームの評価は、次のことを行います。

  1. ship-x-positionというひとつの引数shipを受け取る関数を定義し、 それはshipx-positionを返却します。 ship-y-positionとその他の要素も似たような関数の定義が与えられます。 これらの関数はアクセス関数と呼ばれ、 構造体の要素にアクセスするときに使用されます。

  2. shipは、shipのインスタンスの型の名前になります。 shiptypepで使用可能であり、 例えば(typep x 'ship)xshipのときはtrueに、 xship以外のオブジェクトのときはfalseになります。

  3. ship-pという名前のひとつの引数を取る関数が定義されます。 これはpredicateであり、引数がshipのときはtrueを、 それ以外のときはfalseを返却します。

  4. make-shipと呼ばれる関数が定義され、 起動されると5つの要素を持つ構造体のデータが作成されます。 各要素にはアクセス関数を使うのが適切です。 下記の実行がなされたとき、

(setq ship2 (make-ship))
(setq ship2 (make-ship :mass *default-ship-mass*
                       :x-position 0
                       :y-position 0))
  1. copy-shipと呼ばれる引数をひとつ受け取る関数が定義されます。 引数がshipオブジェクトのとき、新しいshipオブジェクトが生成され 引数のものをコピーします。この関数は「コピー関数」と呼ばれます。

setfshipの変更可能な要素に使うことができます。

(setf (ship-x-position ship2) 100)

これはship2x-position100に変更しています。 これはdefstructの動作として、 各アクセス関数に対してdefsetfを適用するように 生成しているので動作します。

;;;
;;; 例1
;;; 構造体型townの定義
;;; area, watertowers, firetrucks, population, elevation が要素
;;;
(defstruct town
            area
            watertowers
            (firetrucks 1 :type fixnum)    ;初期化スロット
            population 
            (elevation 5128 :read-only t)) ;スロットは変更不可
=>  TOWN
;townインスタンスの作成
(setq town1 (make-town :area 0 :watertowers 0)) =>  #S(TOWN...)
;townのpredicateは新しいインスタンスを認識できる
(town-p town1) =>  true
;新しいtownのareaはmake-townによって設定
(town-area town1) =>  0
;新しいtownのelevationは初期値
(town-elevation town1) =>  5128
;setfはリーダー関数を認識できる
(setf (town-population town1) 99) =>  99
(town-population town1) =>  99
;コピー関数はtown1のコピーを作る
(setq town2 (copy-town town1)) =>  #S(TOWN...)
(= (town-population town1) (town-population town2))  =>  true
;elevationは読み込み専用スロットなのでこの値は
;構造体が作成されたときのみ設定できる
(setq town3 (make-town :area 0 :watertowers 3 :elevation 1200))
=>  #S(TOWN...)
;;;
;;; 例2
;;; 構造体型clownの定義
;;; この構造体は非標準のprefixを使う
;;;
(defstruct (clown (:conc-name bozo-))
            (nose-color 'red)         
            frizzy-hair-p polkadots) =>  CLOWN
(setq funny-clown (make-clown)) =>  #S(CLOWN)
;非標準のリーダーの名前を使う
(bozo-nose-color funny-clown) =>  RED        
(defstruct (klown (:constructor make-up-klown) ;キーワードの変更以外は
            (:copier clone-klown)              ;似たような定義
            (:predicate is-a-bozo-p))
            nose-color frizzy-hair-p polkadots) =>  klown
;変更した構築関数が今存在する
(fboundp 'make-up-klown) =>  true
;;;
;;; 例3
;;; 構造体型vehicleの定義
;;; その後vehicle構造体をincludeした
;;; truck構造体を定義する
;;;
(defstruct vehicle name year (diesel t :read-only t)) =>  VEHICLE
(defstruct (truck (:include vehicle (year 79)))
            load-limit                          
            (axles 6)) =>  TRUCK
(setq x (make-truck :name 'mac :diesel t :load-limit 17))
=>  #S(TRUCK...)
;vehicleリーダーがtruckで動作する
(vehicle-name x)
=>  MAC
;デフォルト値は:include項指定のもの
(vehicle-year x)
=>  79 
(defstruct (pickup (:include truck))     ;pickup型はtruckをinclude
            camper long-bed four-wheel-drive) =>  PICKUP
(setq x (make-pickup :name 'king :long-bed t)) =>  #S(PICKUP...)
;:includeのデフォルト値は継承されたもの
(pickup-year x) =>  79
;;;
;;; 例4
;;; BOA構築の使用
;;;
(defstruct (dfs-boa                      ;BOA構築
              (:constructor make-dfs-boa (a b c)) 
              (:constructor create-dfs-boa
                (a &optional b (c 'cc) &rest d &aux e (f 'ff))))
            a b c d e f) =>  DFS-BOA
;a, b, cは位置によって設定し、&restは初期化しない
(setq x (make-dfs-boa 1 2 3)) =>  #(DFS-BOA...)
(dfs-boa-a x) =>  1
;aとbはセット、cとfはデフォルト
;a and b set, c and f defaulted
(setq x (create-dfs-boa 1 2)) =>  #(DFS-BOA...)
(dfs-boa-b x) =>  2
(eq (dfs-boa-c x) 'cc) =>  true
;a, b, cはセット、&restはdに集められている
(setq x (create-dfs-boa 1 2 3 4 5 6)) =>  #(DFS-BOA...)
(dfs-boa-d x) =>  (4 5 6)

影響

なし。

例外

もし2つのスロットの名前が (直接現れたか、:includeオプションによって継承されたかに関わらず) string=で等しいときは、 defstructは型program-errorのエラーが発生します。

included-structure-nameが構造体型の名前ではないときの結果は未定義です。

参考

documentation, print-object, setf, subtypep, type-of, typep, 3.2. コンパイル

備考

printer-nameはプリンターを制御する *print-escape*のような変数を見なければなりません。

slot-initformと対応するスロットの:typeオプション間では、 型の不一致による警告を禁止する必要があります。 なぜなら、スロットオプションを指定するために slot-initformを指定する必要があり、 場合よっては適切なデフォルト値が存在しないかもしれません。

defstructsetfの使用による スロットのアクセッサを変更する仕組みは 実装依存です。 例えば、これはsetf関数か、setf展開か、 あるいはその他のsetfへの実装コードとして知られている 実装依存の仕組みなどが使われます。


TOP, Github