再利用可能な関数の必要条件
自作の関数をおさめたソースコードのファイルが一つ二つと増えてくると、これらの関数を他のプロジェクトでもそのまま使いたいと思うのは自然な発想です。再利用する関数は今自分が書いているプログラムと渾然一体となって動作するものですから、お互いに変数名(シンボル名)や関数名が衝突しないもの、お互い動作が干渉しない独立性の高いものとなっていなければなりません。例えば、再利用する関数は、別々の関数が同じグローバル変数を別々の目的で使用するようなことは安易には起こらない、安心して使える仕組みにする必要があります。
今までは Visual LISP の機能の紹介でしたが、ここから今回と次回は Visual LISP の機能を超えて関数ライブラリ機能という目標に向かって独自な開発環境の整備の話となります。少しややこしい話かもしれませんが、Visual LISP で発展的な開発を行う環境を整える作業が必要です。
AutoLISP におけるモジュールとは
Common LISP にはモジュールという機能があります。モジュールとは、そのファイルを取り込むことによっていろいろな目的の関数が利用可能になる関数ライブラリの塊です。しかも、モジュールの内部はインターフェースとして公開された関数以外は隠蔽されていて、それを利用するプログラムから内部の動作は保護され、利用する側のプログラムにもモジュールは不測な悪影響を及ぼさない、必要に応じていつでも使える可搬性の高いものです。Common LISP のモジュールには他にも融通を利かせる機能が備わっていますが、簡単なイメージとしては上で述べたようなものとなっています。
このような仕組みが AutoLISP にあれば再利用可能な関数を集めた関数ライブラリになりそうなものですが、言語レベルでそのような機能はあり、、、実は有るのです。
それは【独自の名前空間 VLX アプリケーション】です。これは AutoCAD ユーザーがシステムに組み込んで新しいコマンドを追加するもので、アプリケーションが公開した関数を AutoCAD ユーザーがコマンドとして呼び出して使用します。そして、既存のシステムと干渉しないようにアプリケーションの内部は隠蔽されています。この仕組みは、まさにモジュールそのものです。実は AutoCAD ユーザーは、プログラムを書かないと意識しないかもしれませんが AutoLISP インタプリターを普段からしっかり使っている訳です。なお、名前空間とは変数名などの名前の有効範囲を言い、言葉を変えれば同じ名前空間内で同じ名前の別のものが存在してはならない範囲を言います。そして「独自の名前空間」となると、既存の別のシステムの中に組み込んでも、既存の名前空間からは独立して干渉を受けない世界をもつことになります。具体的に言うと、既存のシステムの中に A という変数名が使用されていても、「独自の名前空間」内にも A という変数名を使用でき、お互い干渉しないということです。
モジュール = AutoLISP の【独自の名前空間 VLX アプリケーション】という構図で、モジュールは AutoCAD ユーザーが利用するものとなれば、AutoLISP プログラミングといえば、その下位であるモジュールとしての【独自の名前空間 VLX アプリケーション】を作成するレベルに位置づけられます。よって、再利用性の高い関数を書いていくためには別の仕組みでお互いに動作が干渉しない、同じ名前空間内で名前がかぶらないものとしなければなりません。そして、ひとまず自身の開発環境すなわち名前空間で動作するプログラムが書けたなら、【独自の名前空間 VLX アプリケーション】というモジュールにパックして AutoCAD ユーザーに提供すれば、どのような環境の AutoCAD でも問題なく動ものとすることができます。
同じ名前空間で同じ名前を避ける方法
お互いの関数の動作が干渉しない独立性の高いものとするには、同じ変数名や同じ関数名を使用しない、そしてそれぞれの関数から呼び出すサポート関数でも同じ名前でそれぞれが別の定義をしないことです。これは言語レベルでサポートされるものではなく、もっと原始的なコードを書いていく過程でプログラマーがルールに従ってこそ実現されるものとなります。ただのルールですから、プログラマーそれぞれが定めて守るものですが、そのルールとなるポイントを以下に述べます。
同じ変数名や同じ関数名を使用しないようにするためには、なるべくその目的に合った、その機能を表す命名をします。目的が異なるならばそれぞれの目的を含んだ名前として、そのため関数名が長くなってもかまいません。また、変数名はほとんどはローカル変数で関数内部に名前空間が限定されますが、グローバル変数を使う場合には、その変数を利用する関数名を絡ませた名前を付けるべきです。関数から呼び出すサポート関数も、それが一般的で無い、その関数から呼び出されることを前提としているならば呼び出し側の関数名を絡めた名前を付けるべきです。
関数名を絡めた名前の付け方は、一定の法則を決めておきます。たとえば、foo 関数が使用するグローバル変数は *foo:var* というように「:」(コロン)を挟んで前置詞のように関数名を付加します。前後の「*」(アスタリスク)は、LISP 一般におけるグローバル変数を表す習慣(これもルール)です。またサポート関数も foo 関数から呼ばれることを前提とした独自のサブ関数といえるものならば foo:boo といったように同じく「:」を挟んで関数名を付加します。このようにしますと foo 関数に関係するものはすべて foo で始まる名前に限定されますので、最初の foo という名前が他のどこかで使われてさえいなければ良い問題になります。そして、foo という名前が目的と機能をよく表したものであるならば、まず同じ名前が他で使用されていることは無いはずです。なお区切りの文字の「:」は別の文字でも良いのですが、Common LISP のモジュールでも同種の意味で使われていますので通りが良いです。
以上のような命名規則で定義した関数の例を以下に示します。AutoCAD の図形から可能なものを分解する関数です。explode 関数が本体の関数で、explode:sub 関数が本体と関連するサブ関数です。分解する対象のオブジェクトタイプの種類を収めたリストをグローバル変数 *explode:target-all* と *explode:target-block* に保持しています。ここでは explode 関数に関係するシンボル名はすべて explode で始まっています。
なお、この explode 関数の使い方は、ename 引数に分解する【図形名】、mode 引数がシンボルで 'ONCE の場合は一度だけ分解し、'BLOCK の場合はブロック参照のみ入れ子を含めてすべて分解します。その他の場合は入れ子になったものを含めて分解可能なものはすべて分解します。戻り値は、分解された後の個々の【図形名】を収めたリストです。
(setq *explode:target-all* '("AcDb3dPolyline" "AcDb2dPolyline" "AcDbPolyline" "AcDbRegion" "AcDbBlockReference" "AcDbPolygonMesh" ) *explode:target-block* '("AcDbBlockReference") ) (defun explode:object (obj / parts) (if (member (vla-get-ObjectName obj) (if (= mode 'BLOCK) *explode:target-block* *explode:target-all* ) ) (progn (setq parts (vlax-safearray->list (vlax-variant-value (vla-Explode obj)))) (vla-Delete obj) (if (= mode 'ONCE) parts (apply 'append (mapcar 'explode:object parts)) ) ) (list obj) ) ) (defun explode (ename mode) (mapcar 'vlax-VLA-object->ename (explode:sub (vlax-ename->VLA-object ename))) )
このような命名規則の導入で、おのおのの関数は独立性の高い再利用可能な関数となりました。次は、こういった関数をライブラリとして一つにまとめておき、必要に応じて呼び出せる仕組み作りを考えます。