• 作成:

Prologの述語に渡される引数が定義されているものなのかmeta_predicateを使って静的にチェックする

やりたいこと

他人に以下のようにPrologプログラムを書いてもらう時、

:- module(find_undefined, [foo/0]).


foo :- bar(buz).

述語barに渡すbazの定義が存在しない場合、 Emacs向けのSweepVSCode向けのVSC-Prologなどの編集支援機構に警告してほしい。

不可能では?

Prologはかなり動的な言語なので、これは不可能な要求のようにも思えます。

しかし、

foo :- assertz(buz).

のようにassertz/1を使うと、

Undefined predicate buz/0, use C-c RET to define it

のように警告してくれます。

assertzはビルドイン関数ですが、何かしらの方法で似たような属性を付けることが出来るのではないかと考えました。

調査

SWI-Prologのソースコードをassertzで検索してみましたが、ビルドインライブラリの定義などにも使われているようなので数が多くて難しいです。

削除する方のretractでも検索してみます。こちらの方が流石に使われている量は少ないと思うので。

ガチャガチャ検索してみた結果、 SWI-Prolog -- library(prolog_xref): Prolog cross-referencer data collection を経由して、 SWI-Prolog -- (meta_predicate)/1 に辿り着きました。 xrefよりはまだ言語機能に近い気がします。

SWI-Prolog以外で動くかは謎。一応GNU Prologには定義があるようですが… Predicate information

実装はどうせ自分でやるため、編集支援機能で正常に動けばそれで良いです。

meta_predicateを使ってみます

警告を出す

:- module(find_undefined, [foo/0]).

:- meta_predicate bar(0).

bar(_).

foo :- bar(buz).

の場合、

Undefined predicate buz/0, use C-c RET to define it

と警告が出てくれます。

警告を消す

さて定義されたものの場合は警告されないようにします。

:- module(find_undefined, [foo/0]).

:- meta_predicate bar(0).

buz.

bar(_).

foo :- bar(buz).

のように雑に述語(事実?)を定義してやれば警告は消えます。

atomではなくなります

しかし、

bar(X) :- atom(X), write(X).

のようにatomの場合writeするようにすると、この場合Xは述語のためfalseを返すようになってしまいます。

またatomじゃなくなるのでパターンマッチにおいても、

:- module(find_undefined, [foo/0]).

% :- meta_predicate bar(0).

bar(buz).

foo :- bar(buz).

では、

?- foo().
true.

となりますが、コメントアウトを外して、

:- module(find_undefined, [foo/0]).

:- meta_predicate bar(0).

bar(buz).

foo :- bar(buz).

とすると、

?- foo().
false.

と動作が変わります。

そもそもatomは「定義」されるものではないので、仕方がないことではあるのですが。

パターンマッチは修飾すれば使えます

パターンマッチは行えないと不便ですね。

以下のように修飾して述語を参照するとパターンマッチも成功します。

:- module(find_undefined, [foo/0]).

:- meta_predicate bar(0).

buz.

bar(find_undefined:buz).

foo :- bar(buz).
?- foo().
true.

find_undefined:buzという修飾名はめっちゃ長く思えますが、実際のプログラムではもっと短い名前を使います。

その短めの名前のモジュールの述語を参照すれば、集合の一つの要素のように扱えるため、下手なatomよりも堅牢なパターンマッチが出来るとも考えられます。

本当はもっと静的にチェックしていきたいのですけれど、その場合はそもそも動的なPrologを使うのが誤っています。 DSLを書くにしても自分でゼロから設計するのが正解ですね。