Previous: 組み込みのオペレーション, Up: オペレーション [Contents][Index]
ASDFは、オブジェクト指向の手法で拡張可能なようにデザインされています。プログラマはoperation
のサブクラスを作って必要な挙動を実装することで、ASDFに新しい機能を追加することができます。
ASDFの組み込みのオペレーションは、ユーザー定義のオペレーションに対して「特権的」な地位にあるわけではありませんが、開発者の方には、独自のオペレーションを追加するときに決してasdf
パッケージを使わないようにお願いします。というのも、名前の衝突は避けるべきですし、かといって我々は「ASDFのオペレーション名を管理するグローバルなレジストリ」をいちいち作りたくもないからです。
[訳者補足]
また、ASDFにはオペレーション・アクション間の依存関係を指定するためのサブクラスとして、downward-operation
、upward-operation
、sideway-operation
、selfward-operation
、non-propagating-operation
の5つが備わっており、すべてのオペレーションはこの内の少なくとも1つを継承するべきです。オペレーションの依存関係を指定する必要がないときも、non-propagating-operation
を継承するようにしてください。operation
のみを継承すると、そのオペレーションを呼んだ時に警告が通知されるでしょう。
例として、組み込みのload-op
を簡略化したmy-load-op
を定義してみましょう:
(defclass my-load-op (downward-operation selfward-operation) ((selfward-operation :initform '(prepare-op compile-op) :allocation :class) ;; (downward-operation :initform nil :allocation :class) ))
non-propagating-operation
を除く4つのクラスはすべて、同名のスロットをクラス変数として備えています。selfward-operation
は「同一コンポーネントに対する別のオペレーション」に依存するオペレーションです。上の例では、コンポーネントfoo
に対してmy-load-op
が呼ばれた時、先だってfoo
にprepare-op
とcompile-op
が適用されます。次に、downward-operation
は「子コンポーネントに対するオペレーション」に依存するオペレーションです。上の例ではdownward-operation
スロットを省略していますが、デフォルトのnil
は同じオペレーションmy-load-op
を意味します。つまり、コンポーネントfoo
に対してmy-load-op
が呼ばれた時、先だってfoo
のすべての子コンポーネントに対してmy-load-op
が適用されます。
asdf::operation-designator
型は、オペレーションオブジェクト・シンボル・クラスのいずれかです。多くの場合、シンボルが使われるでしょう。その際、nil
は「同じオペレーション」を意味します。
downward-operation
は子コンポーネントについての依存関係を持つオペレーションです。baz-op
がfoo-op
のdownward-operation
スロットに指定されているとき、ASDFはコンポーネントparent
に対するアクション(foo-op . parent)
を実行する前に、まずはparent
のすべての子コンポーネントchild
についてアクション(baz-op . child)
を実行します。
upward-operation
は親コンポーネントについての依存関係を持つオペレーションです。baz-op
がfoo-op
のupward-operation
スロットに指定されているとき、ASDFはコンポーネントchild
に対するアクション(foo-op . child)
を実行する前に、まずはchild
の親コンポーネントであるparent
についてアクション(baz-op . parent)
を実行します。
sideway-operation
は依存コンポーネントについての依存関係を持つオペレーションです。baz-op
がfoo-op
のsideway-operation
スロットに指定されているとき、ASDFはコンポーネントc
に対するアクション(foo-op . c)
を実行する前に、まずはc
が依存している(:depends-on
)すべてのコンポーネントd
についてアクション(baz-op . d)
を実行します。
selfward-operation
は同一コンポーネントについての依存関係を持つオペレーションです。baz-op
がfoo-op
のselfward-operation
スロットに指定されているとき、ASDFはコンポーネントc
に対するアクション(foo-op . c)
を実行する前に、まずはアクション(baz-op . c)
を実行します。selfward-operation
スロットには、複数のオペレーションをリストとして指定することができます。
non-propagating-operation
は依存関係を持たない、単独で実行されるオペレーションです。
上で例示したように、これらのオペレーションは多重継承することができます。(もっとも、non-propagating-operation
は単一継承するべきでしょう。)また、依存関係が循環しないようにするのはプログラマの責任に委ねられています。
[訳者補足終わり]
新しく作られたオペレーションには、多くの場合、次の総称関数の内の少なくとも1つについてメソッドを定義しなければならないでしょう。
perform
は何らかの入力ファイルから出力ファイルを作り出すための総称関数です。prepare-op
のように依存関係の伝搬だけするようなオペレーションは別にしても、たいていのオペレーションについてはperform
メソッドが最も重要でしょう。perform
が呼ばれると、(すべての依存関係についてperform
が実行されたあと)ターゲットのコンポーネントに対して処理が実行されます。
perform
メソッドは、その入力と出力のパスを決めるのにinput-files
とoutput-files
(下述)を呼ばなければなりません。ユーザーが、このメソッドを上書きしたり、アウトプットトランスレーションを変更したりできるようにするためです。output-files
は多値を返しますが、perform
は最初の値――出力パス名のリスト――のみを使うべきです。出力ファイルがただ1つと決まっている場合は、代わりに関数output-file
が使えます。output-file
は出力パス名が1つだけであることを保証し、そのパス名を返します。
perform
メソッドが何らかの出力ファイルを伴うならば、その出力先をASDFが定められるようにこのメソッドも定義しなくてはなりません。
output-files
は2つの値を返します。1つ目はパス名のリストであり、2つ目はブーリアンです。ブーリアンがnil
の場合(返り値が1つしかなく、2つ目が暗黙にnil
となる場合も含む)、:around
メソッドがアウトプットトランスレーションに従ってパス名を変換します。例えば、コンパイルされたオブジェクトファイルが処理系依存のキャッシュとして保存される場合などは、アウトプットトランスレーションが使われています。ブーリアンがt
の場合は:around
メソッドで変換されることはありません。
この総称関数は、アクションを引数に取り、依存するアクションをまとめたリストを返します。リストのフォーマットについては下で説明します。
[訳者補足]
上述の5つのサブクラスを継承してオペレーション間の依存関係を指定すれば、component-depends-on
メソッドは適切に構成されるので、通常は明示的にこのメソッドを定義する必要はありません。また、特定のコンポーネント、システムに対して依存関係を記述したい場合も、:in-order-to
オプションを使えば事足りるでしょう。(→ アクションの依存関係) しかし、それらを超えるような複雑な依存関係を指定したい場合――例えば特定のコンポーネント型に対するオペレーションに限って依存関係を追加したい場合など――は、メソッド定義が必要になるかもしれません。
なお、名前の印象に反して、コンポーネント間の依存関係を返す関数ではないことに気を付けましょう。システムの:depends-on
オプションに与えた依存コンポーネントを調べたい場合は、代わりにsystem-depends-on
という総称関数があります。
[訳者補足終わり]
自分でこのメソッドを定義する場合は、メソッドで追加したい内容と(call-next-method)
で返るリストを常にappend
して返すことを強く推奨します。あなたがASDFの内部を熟知しているのでない限り、そうしないと「興味深い」エラーが起こる可能性が高いです。これは概念的には、メソッドコンビネーションのappend
を手動でやっているのと同じことになります。(残念ながら、標準のメソッドコンビネーションを互換的に導入するのは、手遅れでした。)
それでは、返り値について説明します。component-depends-on
が返すリストの各エントリもまたリストです。
各エントリの最初の要素(car
)はオペレーション指示子、つまりオペレーションオブジェクトそのものかオペレーションクラスのシンボル(例えばload-op
、compile-op
、prepare-op
など)であり、後者はmake-operation
でインスタンス化されるでしょう。
各エントリの2番目以降(cdr
)はコンポーネント指示子のリストです。コンポーネント指示子はコンポーネントオブジェクトそのものか、find-component
でコンポーネントオブジェクトに変換される識別子(identifier)です。find-component
は現在の親コンポーネントを第1引数に、識別子を第2引数にして呼び出されるでしょう。識別子は一般的には、文字列か、シンボル(coerce-name
で小文字の文字列になる)か、あるいは文字列・シンボルのリストです。特にnil
は親コンポーネントそのものを指します。
新しく作られたオペレーションには、次の総称関数についてメソッドを定義することができます。
input-files
は、perform
に対する入力ファイルのパス名のリストを返します。
デフォルトのinput-files
のメカニズムは賢くできているので、あなたが自らメソッドを定義する必要はないことのほうが多いでしょう。メソッド定義が必要なのは、最終的な入力ファイル(のまとまり)が複数通り考えられるケースのみです。ほとんどのオペレーションはselfward-operation
を継承していて、selfward-operation
はソースファイルを含むように入力ファイルを適切に設定します。
この総称関数は、アクション(オペレーションとコンポーネントの対)がもう一度perform
されなければならない場合にnil
を返します。デフォルトは常にt
です。
operation-done-p
にメソッドを定義する必要があるのは、ファイルシステム上のタイムスタンプが変わっていなくても前回のオペレーションの実行を無効にしてやりなおすべき状況がある、という場合に限ります。そしてその場合、このメソッドはnil
を返すべきでしょう。
例えば、このメソッドはtest-op
については常にnil
を返します。テストというのは決して完了する性質のオペレーションではないからです。もちろん、単にテストのリポートファイルから結果を読み出すだけのオペレーションとしてtest-report-op
のようなものを定義し、システム定義の:in-order-to
オプションでtest-op
をこのオペレーションに委ねて、何度繰り返しても同じになるようにすることは可能です。その場合はこのメソッドがt
を返すようにしても良いでしょう。
何らかのメッセージを表示するオペレーションは、Lispのコンパイラやローダと同様に、出力先を標準の*standard-output*
ストリームにするべきです。