前の記事の答え(または黒魔術)


文中のプログラムは foo/1 の節が連続していないところに問題がある.処理系によっては何事もなく読み込まれるが,処理系によっては警告が出る.それでも,「警告はエラーにあらず」と言い張れば,ほとんどの処理系では実用上問題ないが,GNU Prolog だけは最初の節から連続していないものは無視される仕様になっており,そのため go/0 が失敗する.

実は Prolog にも ISO の標準規格なるものが(一応)存在して,ずばり「節が連続していない」ことを示す discontiguous という宣言が定義されている.すると,

:- discontiguous(foo/1).

と書けば話が済みそうなのだが,残念ながら XSB にはこれが実装されておらずエラーになる*1.処理系によっては foo/1 を動的述語*2にすれば警告が消える,といった裏技があるが,肝心の GNU Prolog では discontiguous 宣言を入れない限りは後ろの節が無視されるため,上記の宣言はどうしても必要になる.ちなみに,たいていの処理系では foo/1 を囲む括弧は必要ないが,GNU Prolog だけは括弧をつけないとエラーになる.

XSB は :- で始まる行のうち宣言*3にあたるもの以外は query とみなしてプログラムの読み込み時に評価されることになっているので,discontiguous/1 という述語のダミーを用意してごまかすことはできる.とはいえ,

discontiguous(_).

と書けば済むかというと,XSB では確かに動作するようになるが,今度は YAP が組み込み述語を再定義するなと文句を言ってくる.まあ,非標準ながら Prolog にも条件付きコンパイルのような機能があって,たとえば,

:- if current_predicate(discontiguous/1).
discontiguous(_).
:- endif.

のように書けたりもするのだが,Ciao にはこの機能がないため困ったことになる.

いよいよ手詰まりのように見えるが,実はもうひとつだけ手段が残っている.ほとんどの処理系で dynamic/1 をはじめとする各種宣言は組み込み述語になっていて,宣言以外のところでも他の述語と同じように呼び出すことができる*4.そもそも,先の YAP のエラーはこれが原因なのであった.

これを利用すると discontiguous/1 を動的述語として「初期化時に」定義するという芸当ができる.少々ややこしいロジックがいるので,補助述語を定義することにする.

:- initialization(define_discontiguous).

define_discontiguous :-
    current_prolog_flag(dialect,xsb), !,
    dynamic(discontiguous/1),
    assertz(discontiguous(_)).
define_discontiguous.

initialization(Goal) は初期化時に Goal を実行するという意味で,これは ISO の標準規格で定義されており,またどの処理系でも使える.XSB などでは単に Goal の部分だけを書いても普通に評価される,というのは最初のほうで触れたとおりだが,GNU Prolog では :- の後ろには宣言しか書けないので,上のとおりにする必要がある.

さて,これで完成,と言いたいところだが,ここに来て SWI-Prolog に妨害される.というのも,どういうわけか dynamic(discontiguous/1) のところで parser が混乱するらしく syntax error だと言われてしまう.とはいえ,これは適当に obfuscate すれば回避できる.

というわけで,問題の答えは次のとおりである.

:- initialization(define_discontiguous).

define_discontiguous :-
    current_prolog_flag(dialect,xsb), !,
    atom_chars(Name,[d,i,s,c,o,n,t,i,g,u,o,u,s]),
    dynamic(Name/1),
    assertz(discontiguous(_)).
define_discontiguous.

:- discontiguous(foo/1).

まあ,まさに誰得な記事である.

*1:一応,本体部分は読み込まれるので go/0 自体は正しく動作する.

*2:要するに :- dynamic(foo/1). を追加する.

*3:要するに dynamic とか include とか.

*4:問題文中にあげたものだと GNU Prolog 以外は普通の述語として呼び出せる.ほかの有名どころだと SICStus は呼び出し不可.