% 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
...)
構築関数の引数は、全てキーワード引数です。 各キーワード引数のスロットは、 構造体のスロットの名前に対応したキーワードの 名前でなければいけません。 全てのkeywordとformは評価されます。 もしスロットがこの方法で初期化されないときは、 それは構築関数呼び出し時にスロットの定義内の 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は下記の通り。
:type type
:read-only x
setfは受け付けません。
もしxがfalseなら、そのslot-optionは効果を持ちません。
xは評価されません。
下記のキーワードオプションは、defstructで使うことができます。
defstructオプションは、
キーワードか、キーワードとそのキーワードの引数のリストの
どちらかを指定できます。
キーワード自身が指定されたときは、
そのキーワードと引数なしのリストを指定したことと同じです。
defstructオプションの構文は、
slot-optionが使われたペアの構文と違っています。
これらのオプションどのどの部分も評価されません。
:conc-name
:conc-nameは別のプレフィックスを使用するときに指定します。
もしセパレーターとしてハイフンを使用したいのであれば、
プレフィックスの部分としてそれを指定しなければなりません。
もし:conc-nameがnilのときか引数を指定しなかった場合は、
プレフィックスは使用されません。
したがって、リーダー関数の名前はスロットの名前と同じになります。
もしプレフィックスにnil以外が指定されたとき、
各スロットのリーダー関数の名前は、
プレフィックスとスロットの名前を結合したもので構築され、
その結果のシンボルをdefstructフォームが展開された時点での
現在のパッケージにinternしたもにになります。
:conc-nameに何を与えても、
プレフィックスの付いていないスロット名とのキーワードが
コンストラクタ関数で使用されることに注意してください。
リーダー関数の名前は、setfの引数にも使用されます。
下記に例を示します。 (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
:conc-nameオプションが明に指定されたかどうかに関わらず、
次の規則が生成されるリーダー(あるいはアクセッサ)の名前の
衝突を管理します。
ある構造体の型をS1、
スロットの名前がX1であり、
そのリーダー関数の名前がRとします。
これは他の構造体のS2によって継承されています。
S2はスロットX2があり、
同じ名前のリーダー関数Rを指定しています。
S2はまだRの定義を生成しておらず、
かわりにS1の定義から定義Rが継承されています。
(このような場合、もしX1とX2が違うスロットのときは、
実装はstyle-warningを通知するかもしれません)
:constructor
nilではないとき、
その引数は構築関数の名前を指定したシンボルです。
もし引数が指定されなかったとき
(あるいはもし自身のオプションが指定されなかったとき)
構築の名前は、文字列"MAKE-"と構造体の名前を結合したものを生成し、
その名前をdefstructフォームが展開された時点の
現在のパッケージにinternしたもにになります。
もし引数がnilで与えられたときは、
構築関数は定義されません。
:constructorが(constructor name arglist)として与えられたとき、
キーワード駆動の構築関数の作成のかわりに、
defstructは「位置的な」構築関数を定義します。
それは引数の位置かあるいは場合によってはキーワードによって
決定される意味を持つ引数を取ります。
arglistはどの引数を構築するかを定義したものとして使われます。
単純な場合として、
例えば(:constructor make-foo (a b c))のようなものは、
make-fooを3つの引数の構築関数として定義し、
それらの引数はスロットの名前a, b, cの初期化に使用されます。
boa構築として知られています。
boa構築がどのように処理されるかの情報は、
3.4.6. boaラムダリストをご確認ください。
:constructorオプションは複数許容され、
それぞれ違ったパラメーターを取る
いくつかの構築関数を定義することができます。
defstructは
:constructorオプションが明に指定されていないときか、
:constructorオプションが名前の引数なしに指定されていたときのみ
デフォルトの名前をキーワードの構築関数を作成します。
(:constructor nil)は、
他の構築関数のオプションが指定されていないときにのみ意味します。
それはdefstructによる他の全ての構築関数の生成を禁止します。
defstructは、
指定された各:constructorオプションにしたがって
構築関数を生成します。
これは複数のキーワードによる構築関数を指定するのと同様に、
複数のboa構築を指定することも許されます。
:copier
"COPY-"と構造体の名前を結合したものを生成し、
その名前をdefstructフォームが展開された時点の
現在のパッケージにinternしたもにになります。
もし引数がnilで与えられたときは、
コピー関数は定義されません。
defstructの:typeオプションが使われなかったときは、
下記のものと同等になります。
(copier-name x) = (copy-structure (the structure-name x))
:include
(defstruct person name age sex)
astronautは、
name, age, sexという属性と、
person構造体の操作関数、
astronautが定義した:includeを
次のようにして作成します。(defstruct (astronaut (:include person)
(:conc-name astro-))
helmet-size
(favorite-beverage 'tang))
:includeは、その構造体がincludeされた構造体と
同じスロットを持つように定義するものです。
このような方法で作成されたものは、
includeされた構造体のリーダー関数もまた、
今回定義した構造体で使用できます。
この例では、したがってastronautでは5つのスロットを持ちます。
その内訳は、3つがpersonの定義であり、
2つがastronaut自身のものです。
person構造体によって定義されたリーダー関数は、
astronaut構造体のインスタンスでも適用でき、
そしてそれは正しく動作します。
さらにastronautはそれらの要素に対する
自分自身のリーダー関数も定義されます。
下記の例は、astronaut構造体を使用したものです。(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シーケンスの
; 年齢の合計を得ます
person-nameとastro-nameの違いは、
person-nameはastronautを含むどのpersonにも正しく適用できますが、
astro-nameはただastronautのみ正しく適用できます。
実装はリーダー関数を使用するときに正しく確認しないかもしれません。
defstructに
多くてもひとつの:includeを指定できます。
:includeの引数は要求され、
以前に定義された何かの構造体の名前でなければなりません。
もし構造体の定義に:typeオプションがないときは、
includeされる構造体もまた:typeオプションを
指定されていてはいけません。
もし構造体の定義に:typeオプションがあるときは、
includeされる構造体は
同じ表現の型を指定した:typeオプションで
定義されていなければなりません。
:typeオプションがないとき、
includeされた構造体の名前はデータ型の名前であり
typepによって認識できる正当な型指定子なので、
これはinclude構造体のサブタイプになります。
上記の例の場合、astronautはpersonのサブタイプです。
したがって、 (typep (make-astronaut) 'person) => true
personの全ての操作がastronautでも動作することを示しています。
:includeを使用した構造体は、
その:includeオプションを使用することで、
include構造体が指定したものとは違ったスロットの
デフォルト値かslot-optionをしていることができます。
例えば下記の通り。(:include included-structure-name slot-description*)
astronautのageをデフォルトを45にしたものは下記の通り。 (defstruct (astronaut (:include person (age 45)))
helmet-size
(favorite-beverage 'tang))
:includeを:typeオプションと一緒に使用したとき、
その効果は、まず最初にinclude構造体の表現で必要な
要素の数だけスキップします。
さらに:initial-offsetオプションの指定による
追加の要素をスキップします。
そのあとで、この点から要素の確保が始まります。
例えば下記の通り。(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)
:initial-offset
(defstruct (binop (:type list) (:initial-offset 2))
(operator '? :type symbol)
operand-1
operand-2) => BINOP
make-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の動作と返却値を下記に示します。(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)
nilの要素は、
binopの定義にある:initial-offsetの2のものです。
次の4つの要素は構造体の名前と、
binopの3つのスロットが含まれています。
:named
:nameは、構造体に名前を付けるよう指定します。
もし:typeが指定されていないときは、
構造体は常に名前付けられます。
(defstruct (binop (:type list))
(operator '? :type symbol)
operand-1
operand-2) => BINOP
make-binopという構築関数と3つの選択関数である
binop-operator, binop-operand-1, binop-operand-2を定義します。
(しかし下記に説明する理由にてbinop-pは定義されません)
make-binopの効果は単純に3つの長さのリストを構築します。(make-binop :operator '+ :operand-1 'x :operand-2 5) => (+ X 5)
(make-binop :operand-2 4 :operator '*) => (* NIL 4)
list関数のようですが、
キーワード引数を受け取ることと、
binopというデータ型という概念に適用した
スロットの標準値を用意する点が違っています。
同じように、3つの選択関数である
binop-operator, binop-operand-1, binop-operand-2は、
それぞれcar, cadr, caddrと本質的に同等です。
これらは完全には同等ではないでしょう。
なぜなら、例えばある実装では
各選択関数の引数が長さ3のリストであるかどうかを
保証するためのエラーチェックを追加するような
正当化を行うかもしれません。
binopはデータ型という概念であって、
Common Lispの型システムの一部ではありません。
typepはbinopを型指定子として認識しませんし、
type-ofはbinop構造体を与えても
listと返却します。
make-binopで構築されたデータ構造と、
正しい構造を持つその他のリストを
区別する方法はありません。
make-binopによって作成された構造体から
構造体名binopを知る方法はありません。
これは構造体に名前がついているときのみ行うことができます。
名前付き構造体は、その構造体のインスタンスが与えられたときに、
構造体名(型の名前)を確実に復元できるという性質を持っています。
:typeオプションなしで定義された構造体では、
構造体名は実際にCommon Lispのデータ型システムの一部になります。
このような構造体をtype-ofに適用したとき、
そのオブジェクトの型の構造体名が返却されます。
typepは正当な型指定子として構造体名を認識します。
:typeオプションと一緒に定義された構造体について、
type-ofは、listか(vector t)のように
指定した:typeオプションに依存したものが返却されます。
構造体名は有効な型指定子にはなりません。
しかし、もし:namedオプションが指定されていたときは、
構造体(defstructの構築関数によって作成されたもの)は、
常に構造体名を含みます。
これは構造体のインスタンスから構造体名を知ることができるということと、
概念的な型に対しての適切なpredicateを
定義することができるということです。
自動的に定義されるname-pという構造体のpredicateは、
最初にその引数が正当な型(list, (vector t), その他)で
あるかどうかを確認し、 そのあと最初の要素が
適切な型の名前を含んでいるかどうかを確認します。
binopを考えます。
これは:namedオプションを追加しただけのものです。(defstruct (binop (:type list) :named)
(operator '? :type symbol)
operand-1
operand-2) => BINOP
make-binopと3つの選択関数
binop-operator, binop-operand-1, binop-operand-2が定義されます。
make-binopの効果は、今回のものは4つの長さのリストを構築します(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
binopという名前はまだtypepで識別できるような
有効な型指定子ではありませんが、
しかし少なくともbinop構造体と他の似たような定義の構造体と
区別をつける方法はあります。
:predicate
predicateの名前を指定したものです。
もし引数が指定されないか、オプション自身が指定されなかったときは、
predicateの名前は、構造体の名前と文字列"-P-"を結合したものを生成し、
その名前をdefstructフォームが展開された時点の
現在のパッケージにinternしたもにになります。
もし引数がnilで与えられたときは、
predicate関数は定義されません。
predicateを定義できるのは構造体に名前がついているときであり、
もし:typeが指定されて、かつ:namedが指定されなかったときは、
:predicateは指定しないかあるいは:nilの値を
指定するかのどちらかでなければなりません。
:print-function, :print-object
:print-functionと:print-objectのオプションは、
型structure-nameの構造体のための
print-objectメソッドを生成します。
これらのオプションは同義語ではありませんが、
似たような機能を提供します。
これらのオプションの選択(:print-functionか:print-object)は、
どのようにしてprinter-nameという関数を呼び出すのかに影響します。
これらのオプションはただひとつだけ使うことができます。
そしてこれらのオプションは:typeが指定されなかったときのみ使用できます。
もし:print-functionオプションが使われたとき、
構造体の名前structure-nameが印字されるときに
指定されたprinter関数が次の3つの引数と共に呼び出されます。
*print-level*と比較することで
信頼性を高めることができます。(:print-function printer-name)の指定は、
おおよそ次のものと同一です。
(defmethod print-object ((object structure-name) stream)
(funcall (function printer-name) object stream <<current-print-depth>>))
<<current-print-depth>>は、現在の印字がどれくらい深いのかという
プリンターの値を表現したものです。
<<current-print-depth>>が常に0であり、
*print-level*がnilでない場合、
印刷が再帰的に下降するにつれて順次小さな値に再束縛されるか、
あるいは、同じ走査中に印刷が再帰的に下降するにつれて
current-print-depthの値が変化し、
*print-level*が一定のままであるかは
実装依存です。
もし:print-objectオプションが使われたとき、
構造体の名前structure-nameが印字されるときに
指定されたprinter関数が次の2つの引数と共に呼び出されます。
(:print-object printer-name)の指定は、
おおよそ次のものと同一です。
(defmethod print-object ((object structure-name) stream)
(funcall (function printer-name) object stream))
:typeオプションが指定されなかったとき、
さらに:print-functionか:print-objectオプションが指定され、
そのprinter-nameが指定されなかったときは、
print-objectメソッドは
structure-nameでspecializedされたメソッドを生成し、
実装の関数である#S表記を用いた構造体の
標準的な印刷を呼び出します。
22.1.3.12. 構造体の印字をご確認ください。
:print-functionと:print-objectの
どちらのオプションも指定されなかったときは、
defstructはstructure-nameに特化した
print-objectメソッドは生成せず、
:includeオプションによって指定された構造体のものか、
あるいは構造体の印刷のデフォルトの動作のものかが
標準的なふるまいとして継承されます。
print-object関数と、22.1.3.12. 構造体の印字をご確認ください。
*print-circle*がtrueのとき、
ユーザーが定義した印刷関数は
オブジェクトを印刷するときに
指定されたストリームを用いてwrite, prin1,
princ, formatを使用できますが、
循環構造を検出したときは#n#構文を用いた印刷がされます。
これは、print-objectに
:print-functionオプションで追加されたメソッドを適用できます。
もしユーザーが定義した印刷関数が
指定されたもの以外のストリームに対して行われたとき、
循環構造の検出はそのストリームでやり直しされます。
*print-circle*変数をご確認ください。
:type
:typeは構造体が使用する表現を明確に指定します。
この引数は、下記のどれかでなければなりません。
vector
(vector t)として指定された結果と同じものが生成されます。
この構造体は一般的なvectorとして表現され、
vectorの要素に値が保存されます。
もし構造体に:nameが指定されていたときは、
最初の要素はvectorの1番目の要素であり、
指定されていないときは0番目の要素です。
(vector element-type)
vectorとして表現され、
vectorの要素に値が保存されます。
全ての要素は指定された型のvectorに
格納できるような型でなければなりません。
もし構造体に:nameが指定されていたときは、
最初の要素はvectorの1番目の要素であり、
指定されていないときは0番目の要素です。
list
このオプションの指定は、
特定の表現へ強制する効果があり、
defstructで指定された順序で
対応する連続した要素を指定した表現で
格納する効果があります。
これは構造体名がtypepによって識別できる
有効な型指定子になることも禁止します。
例えば下記の例を考えます。
(defstruct (quux (:type list) :named) x y)
list関数を使用したかのように、
carがquuxであるようなリストを生成する
構築関数を作らなければいけません。
(deftype quux () '(satisfies quux-p))
(typep (make-quux) 'quux)
(typep (list 'quux nil nil) 'quux)
:typeが指定されなかったときは、
構造体は型structure-objectの
オブジェクトとして表現されます。
:typeオプションがないdefstructは、
構造体名のクラスを定義します。
構造体のインスタンスのメタクラスはstructure-classです。
defstructの構造体を再定義したときの結果は未定義です。
defstructに何もオプションを指定しなかった場合は、
新しい構造体のインスタンスでは
次のような操作を行う関数が自動的に定義されます。
predicate
-pという名前のpredicateが定義され、
構造体の型のメンバーかどうかをテストします。
predicateの呼び出し(structure-name-p object)は、
objectがその型であればtrueを、
そうでなければfalseを返却します。
typepもまた新しい型の名前を
objectがその型に属したものかどう
かテストする際に使用できます。
このような関数の呼び出しは
(typep object 'structure-name)というフォームを持ちます。
structure-name-slot-nameという名前で生成されます。
この関数は指定されたスロットの内容を読み込みます。
各リーダー関数は引数に構造体型のインスタンスをひとつ取ります。
setfは、変更可能なスロットに対して
リーダー関数を指定することで使用できます。
make-structure-nameという名前で定義されます。
この関数は構造体型の新しいインスタンスを生成した返却します。
copy-structure-nameという名前で定義されます。
コピー関数は構造体型のオブジェクトを引数に取り、
同じ型の新しいオブジェクトを作成してそのコピーを行います。
コピー関数は元のものと全体の構成が同じである新しい構造体を作ります。
2つの構造体インスタンスの各要素はeqlで等しくなります。
もしdefstructフォームがトップレベルフォームとして現れたとき、
コンパイラーは構造体の型を後続の宣言(例えばdeftype)で
有効な型として認識できるようにし、構造体のスロットのリーダーを
setfで使用可能にしなければなりません。
さらにコンパイラーは同じファイルで構造体名を参照する
別のdefstruct定義で:includeを使用できるように
構造体の型についての十分な情報を保存しなければなりません。
defstructが生成する関数はコンパイル時の環境で
定義する必要はありませんが、 コンパイラーは後続のinline呼び出しのために、
関数のコードに関する十分な情報を保存する必要があるかもしれません。
#Sリーダーマクロは、コンパイル時に新しく定義された構造体型については
認識するかもしれませんししないかもしれません。
構造体の定義の例を下記に示します。
(defstruct ship
x-position
y-position
x-velocity
y-velocity
mass)
これは全てのshipが5つの名前を持った要素持つ
オブジェクトであると宣言しています。
このフォームの評価は、次のことを行います。
ship-x-positionというひとつの引数shipを受け取る関数を定義し、
それはshipのx-positionを返却します。
ship-y-positionとその他の要素も似たような関数の定義が与えられます。
これらの関数はアクセス関数と呼ばれ、
構造体の要素にアクセスするときに使用されます。
shipは、shipのインスタンスの型の名前になります。
shipはtypepで使用可能であり、
例えば(typep x 'ship)はxがshipのときはtrueに、
xがship以外のオブジェクトのときはfalseになります。
ship-pという名前のひとつの引数を取る関数が定義されます。
これはpredicateであり、引数がshipのときはtrueを、
それ以外のときはfalseを返却します。
make-shipと呼ばれる関数が定義され、
起動されると5つの要素を持つ構造体のデータが作成されます。
各要素にはアクセス関数を使うのが適切です。
下記の実行がなされたとき、
(setq ship2 (make-ship))
ship2には新しいshipオブジェクトがセットされます。
初期値を希望する要素に供給したい場合は、
make-shipの呼び出しにキーワード引数を使います。
例えば下記の通り。(setq ship2 (make-ship :mass *default-ship-mass*
:x-position 0
:y-position 0))
shipが構築され、3つの要素に初期化されます。
この関数は新しい構造体を作成するので「構築関数」と呼ばれます。copy-shipと呼ばれる引数をひとつ受け取る関数が定義されます。
引数がshipオブジェクトのとき、新しいshipオブジェクトが生成され
引数のものをコピーします。この関数は「コピー関数」と呼ばれます。setfはshipの変更可能な要素に使うことができます。
(setf (ship-x-position ship2) 100)
これはship2のx-positionを100に変更しています。
これは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を指定する必要があり、
場合よっては適切なデフォルト値が存在しないかもしれません。
defstructがsetfの使用による
スロットのアクセッサを変更する仕組みは
実装依存です。
例えば、これはsetf関数か、setf展開か、
あるいはその他のsetfへの実装コードとして知られている
実装依存の仕組みなどが使われます。