反復的な処理をPrologで記述する場合は再帰を使う。その際にはわりとお定まりの形式で幾つか述語を余計に用意してやる必要があり、書く方は若干の疎ましさを感じないわけでもない。LISP系言語であればマクロを利用してパターンを展開するであろうが、Prologも同様である。簡単な例を挙げると、次のような展開が欲しい。
% BEFORE
?- write('L: '), (foreach(X, L) do write(X), write(' ')).
% AFTER
temp_aux([]) :- !.
temp_aux([X | T]) :- !, write(X), write(' '), temp_aux(T).
?- write('L: '), temp_aux(L).
複数の処理をまとめて一回の反復処理とする為に、述語do/2の右辺は結合性を持つ必要がある。演算子テーブルには次のように登録する。
:- op(1100, xfy, do).
続いて反復の度に束縛の実体を変更する必要があるので、引数と展開する反復とをセットにして再帰処理に持たせてやる。
(foreach(E, L) do Template) :- !, do_loop(L, iter(E, Template)).
各反復はリストの中身を一つづつ取り出して、これを変数に束縛し、再帰する。
do_loop([], _Template) :- !. % 終了条件
do_loop([Value | Rest], Template) :-
!, copy_term(Template, Copy), % 反復毎に束縛する環境は複製する
Copy = iter(Value, Goals), % 単一化で引数にヘッドで取出した値を束縛
call(Goals), % 処理を実行
do_loop(Rest, Template). % 再帰
反復処理のコピーに対して束縛を行う事がポイントである。copy_term/2は必ずユニークな変数を割り振ってくれるので、いわゆる健全なマクロが実現される。foreach/2はかなり単純な処理であるからメリットは弱いかもしれないが、for-macroも同様の手法で作成出来る。do/2を汎用化して色々な反復子を許可したい場合は次のような一般化を行うのが定石である。
(Spec do Template) :-
get_spec(Spec, Env, Iterator), !,
do_loop(Env, iter(Iterator, Template)).
get_spec(foreach(E, L), L, E)) :- !.
get_spec(for(Init, Cond, Succ), ..) % 若干複雑なので略
マクロがLISPの専売特許だと思うのは井の中の蛙である。しかしPrologの問題はパフォーマンスである。
0 件のコメント:
コメントを投稿