Prolog 写経記 まとめ

そんなわけで (どんなわけで?),無事に写経を終えたわけなので,簡単にまとめを.
まず,いい意味で一番衝撃だったのはこれ.

delete(X, List1, List2) :-
	conc(L1, [X|L2], List1),
	conc(L1, L2, List2).

リスト List1 から要素 X を削除するのですが,それをリストの連結でやってしまうというこの定義.
宣言的っていうのはこういうことか!?
という衝撃を受けましたねぇ.うーん,これには本当にびっくりした.


もう一つ挙げるなら,先日写経したばかりのこれ.

:- op(900, fx, if).
:- op(850, xfx, then).
:- op(800, xfx, else).
if P then Q :-
	if_then_else(
		Q = (R else S),
		if_then_else(P, R, S),
		if_then(P, Q)).

if_then(P, Q) :-
	call(P), !,
	call(Q).
if_then(_, _).

if_then_else(P, Q, _) :-
	call(P), !,
	call(Q).
if_then_else(_, _, R) :-
	call(R).

これは全然宣言的じゃないのですが,ずっと「Prolog って不自由だなぁ」と思っていた自分の認識が間違っていて,アイディア次第でいくらでも強力なことができるんじゃないかと考え直すきっかけになりました.


一方で激しく落胆したのはこれ.

random(Min, Max, Num) :-
	integer(Min),
	integer(Max),
	Min =< Max,
	retract(seed(S)),
	Num is S mod (Max - Min + 1) + Min,
	NewSeed is (125 * S +1) mod 4096,
	asserta(seed(NewSeed)), !.

seed(7).

Prolog データベースをグローバル変数のように使ってしまうというこのコードには本当に落胆しました.
Prolog ではこういうことが一般的だとか不可避なのであれば,Prolog なんて捨ててもいいかなと思ったくらい.
逆に,Prolog でもっとうまく乱数を扱うにはどうすればいいのか,いろいろ考えるきっかけになったので違う意味ではありがたい存在だったかも.
この乱数についてはエントリを改めて書いてみたいと思います.


ともあれ (JW),お世話になりました>「Prolog ユーティリティライブラリ (isbn:4303717002)」


Prolog での乱数

そんなわけで (どんなわけで?),さっそく乱数の話題.
先のまとめで書いたように,写経していて一番激しく落胆したのが乱数を生成する次の述語です.

random(Min, Max, Num) :-
	integer(Min),
	integer(Max),
	Min =< Max,
	retract(seed(S)),
	Num is S mod (Max - Min + 1) + Min,
	NewSeed is (125 * S +1) mod 4096,
	asserta(seed(NewSeed)), !.
seed(7).

これがひどいのは,乱数の元になる種を述語として Prolog データベースに登録していることです.
要するに,Prolog データベースをグローバル変数として使っているというわけ.
をいをい,Prolog って副作用のある,破壊的代入はできないんじゃなかったのかい!?
ましてやグローバル変数とは.
本当に本当に,許し難いくらいひどいコードだと思いましたよ.

オブジェクト指向っぽく

そんなわけで (どんなわけで?),もうちょっとマシに乱数を扱えないか考えてみました.
まず,乱数の何が課題かというと,これはいわゆる関数のように決まった値を返すわけではない,というところですね.
Prolog の場合は関数じゃないので言い換えると,一つの事実に定まらない,というべきか.
そこで Prolog データベースに次々と変わる種を憶えているわけです.


これが Java なんかだと,種をインスタンスの状態として保持することができるので,全く問題ないわけです.
まぁ,これは副作用を普通に使えるからですが.
んで,まずはこれを Prolog でまねできないか考えてみます.
基本的に副作用のない Prolog なので,値が次々に変化するインスタンス変数というのは使えませんが,代わりに,新しいインスタンスを返すことならできます.
これは「オブジェクト指向入門 (isbn:4756100503)」でスタックを抽象データ型として扱う際に使われていた手法です.
身近なところだと,JavaBigDecimal なんかがそうですね.
BigDecimal は他のインスタンスと加算したり減算したりすることができますが,その際には新しいインスタンスが返されます.

BigDecimal result = foo.add(bar).

こういうやり方であれば,Prolog でも十分表現できるはず.
んで,こうなりました.

random_create(Seed, Min, Max, Value, Next) :-
	random_next(rand(Seed, Min, Max), Value, Next).

random_next(rand(Seed, Min, Max), Value, Next) :-
	Value is Seed mod (Max - Min + 1) + Min,
	NewSeed is (125 * Seed + 1) mod 4096,
	Next = rand(NewSeed, Min, Max).

種と上限・下限をインスタンス変数のように rand/3 に保持します.
そのコンストラクタに相当するのが random_create/4.第 4 引数で作成したインスタンス (rand/3) を返します.
そして random_next/3 は第 1 引数で与えたインスタンスから新たな乱数を生成して第 2 引数に,そして次の乱数を生成するためのインスタンスを第 3 引数に返します.
写経本では 1〜100 までの乱数を生成して,77 が出てくるまで繰り返すのが使用例でしたが,同じことをこれでやるとこんな感じ.

random_test(Seed) :-
	random_create(Seed, 1, 100, Value, Next),
	random_print(Value, Next).

random_print(77, _) :- !,
	write(77), nl.
random_print(Value, Rand) :-
	write(Value), nl,
	random_next(Rand, Value1, Next),
	random_print(Value1, Next).

無限リストで

他に手はないかなということで,同じく副作用のない Haskell ではどうするのかぐぐってみたりしたら (本は積みっぱなし),どうやら遅延評価で無限の長さを持つリストを生み出すらしい.なるほど...
そういえば,並列論理型言語である KL1 や GHC (Guarded Horn Clauses,Haskellコンパイラじゃないよ) でも無限長のリストを活用するみたい.
つまり,遅延評価か並列のどちらかがあれば無限長のリストが使えるようなのですが,Prolog は少なくとも並列言語ではないはず.
そこで遅延評価を探したら,ありましたよ.「Prolog 入門 (isbn:4274073084)」に説明があって,freeze/2 という述語があるとのこと.
それを SWI-Prolog でも使えるか探してみたら,さらにそれを一般化した when/2 という述語がありました.
これを使うと,ある変数が具体化されたときに実行される述語を登録することができるようです.
どこに登録されるかというと,その変数自身.結局副作用? (^^;
まぁいいや,とりあえず使ってみました.こうなりました.

random_create(Seed, Min, Max, [Value | Rest]) :-
	Value is Seed mod (Max - Min + 1) + Min,
	NewSeed is (125 * Seed + 1) mod 4096,
	when(nonvar(Rest), random_create(NewSeed, Min, Max, Rest)).

random_next(List) :-
	List = [_ | _].

random_create/4 に種の初期値と上限・下限を与えると,リストを返します.
このリストの car (Value) は乱数に具体化されていますが,cdr (Rest) は具体化されていない変数です.
その具体化されていない変数 Value には when/2 を使ってこの述語自身を遅延評価するようにしています.
つまり,リストの cdr が具体化されると,その car が新たな乱数になると同時に cdr が具体化された際にまたまたこの述語が遅延評価されるように設定されます.
ちょっと面白いのは,最初に random_create/4 を呼び出したときの第 1〜3 引数が,その呼び出しが終わった後もずっと使われ続けること.一種のクロージャ? これもじっくり考えてみると面白いかも〜.
んで,リストの cdr を具体化するのが random_next/1.これが必要なのがちょっと残念.
この程度なら述語を用意しなくてもいいかとも思ったのですが,それもどうかということで.
本当は,変数が具体化された時じゃなくて,参照されたときに述語が呼び出されると最高なんですけどね.残念!!
ともあれ,無限リスト版の使用例.

random_test(Seed) :-
	random_create(Seed, 1, 100, L),
	random_print(L).

random_print([77 | _]) :- !,
	write(77), nl.
random_print([Value | Rest]) :-
	write(Value), nl,
	random_next(Rest),
	random_print(Rest).

ちょっとすっきり♪

データ駆動だ

さらに,GHC や KL1 を調べていたら,並列に実行する戦略として「データ駆動」と「要求駆動」というのが出てきました.
これらはデータを生成するプロセスと消費するプロセスをどのように同期させるかに関するものです.
「データ駆動」では,データを生成するプロセスが消費するプロセスを駆動します.
「要求駆動」では,データを消費するプロセスが生成するプロセスを駆動します.
ふむ.
上で試した二つの方法は,いずれも「要求駆動」と言えそうです.乱数を消費する側が生成する側を呼び出しているので.
そんなわけで (どんなわけで?),「データ駆動」っぽいやり方も試してみましょう.
つまり,乱数を生成する述語が乱数を消費する述語をコールバックすればいいわけですね.
こうなりました.

random_create(Seed, Min, Max, F) :-
	random_next(Seed, Min, Max, F, _).

random_next(_, _, _, _, End) :-
	nonvar(End), !.
random_next(Seed, Min, Max, F, _) :-
	Value is Seed mod (Max - Min + 1) + Min,
	P =.. [F, Value, End],
	P,
	NewSeed is (125 * Seed + 1) mod 4096,
	random_next(NewSeed, Min, Max, F, End).

random_create/4 は,種の初期値に上限・下限,そして乱数を消費する 2 引数の述語の名前を受け取ります.
乱数を消費する述語の引数は乱数と終了フラグ.終了フラグを具体化して返すと乱数の生成は終了します.
そんなわけで (どんなわけで?),使用例.

random_test(Seed) :-
	random_create(Seed, 1, 100, random_print).

random_print(77, End) :- !,
	write(77), nl,
	End = true.
random_print(Value, _) :-
	write(Value), nl.

かなりすっきり♪


いろいろなやり方がありますね.


Prolog でメモ化

ちょっと前に,「メモ化」なるものがそこかしこで話題になっていました.
んで,すっかり乗り遅れてますが,Prolog でちょっとやってみようかと.
でもでも,メモ化はともかく不動点の方は脳みそがとけちゃうので無理っぽい.何言ってるか分からないんだもの.(;_;)


ともあれ (JW),ある述語をメモ化する述語は結構簡単に書けます.

memoize(P, M) :-
	P =.. [_ | Arg],
	P2 =.. [M | Arg],
	asserta((P2 :- P, asserta(P2))).

これは述語 P をメモ化した述語を M という名前で作成します.
例.

14 ?- memoize(fib(N, F), mfib).

N = _G338
F = _G339 

Yes
15 ?- mfib(10, F).

F = 89 

Yes
16 ?- trace.

Yes
[trace] 16 ?- mfib(10, F).
   Call: (7) mfib(10, _G442) ? creep
   Exit: (7) mfib(10, 89) ? creep

F = 89 

Yes

ここでは fib/2 をメモ化した述語を mfib という名前で作成しています.ちなみに 3 引数の述語でも 4 引数の述語でもメモ化できます.
そして 2 度目の呼び出しをトレースしていますが,再帰呼び出しなどすることなく,一撃で結果を得ています.
これは,mfib(10, 89) という事実が Prolog データベースに登録されているから.
Prolog データベースの乱用はどうかと思うのだけど,これは有効な使い方という気がするから不思議.(^^;


しかし,このメモ化述語には欠陥があります.
というのも,上でメモ化している fib/3 はこんな定義なのですが,

fib(0, 1).
fib(1, 1).
fib(N, F) :-
	N1 is N - 1,
	N2 is N - 2,
	fib(N1, F1),
	fib(N2, F2),
	F is F1 + F2.

メモ化されたのはあくまでも mfib/2 であって fib/2 ではないので,この fib/2再帰呼び出しはメモ化の恩恵を受けることができないのです.
この間盛り上がっていた方面はここから不動点関数とやらに向かっていきましたが,そういう素養のないおいらは別の手段でこれに対処するのだ.


要するに,メモ化した述語を別途作成するのではなく,その述語自身の新しい節として Prolog データベースに登録しちゃえばいいんじゃないかと.
つまり,Prolog データベースに登録されている節は最初こうなっているわけですが...

  • fib(0, 1).
  • fib(1, 1).
  • fib(N, F).

これをメモ化すると

  • fib(N, F). ← これはメモ化する節.
  • fib(0, 1).
  • fib(1, 1).
  • fib(N, F). ← これは値を求める節.

となって,例えば fib(2, F). を求めた直後には

  • fib(2, 2). ← 登録された節
  • fib(N, F). ← これはメモ化する節.
  • fib(0, 1).
  • fib(1, 1).
  • fib(N, F). ← これは値を求める節.

という具合に,新たな値が求められるたびに,その値を事実として持つ節を上に追加していけばいいはず.
そうすると,値を計算する節が再帰呼び出しをしてもちゃんとメモされた節が使われます.


問題は,メモ化する節が値を求める節を呼び出す方法.
普通に再帰呼び出しすると,また自分に来ちゃいます.
といってバックトラックすると,計算された結果を得ることができません.
うーみゅ...
基本はこの二つの組み合わせしかないはず.
つまり,値をメモするために再帰呼び出しして,そこではバックトラックして値を計算する述語に向かう.
そのためには,メモ化する節はバックトラックすべきかどうかを判断できなければならないわけで...
Prolog ユーティリティライブラリ」ならここで Prolog データベースを使うに違いない!!
し・か・し
おいらにはもっとモダンになった Prolog 処理系があるのだ.
実は先の乱数で使った when/2 なんですが,これって内部では変数のアトリビュートというものを使います.
今時の Prolog の変数というのは,変数自身のインスタンス変数としてマップ (JavaMap) を持っているようなものなのです.
つ・ま・り
変数にアトリビュートがあるかどうかをチェックすれば,再帰呼び出しかどうかを判断できるはず.
結局副作用というか論理プログラミングの範疇から外れるわけですが.心より恥じる.
ともあれ (JW),こうなりました.

memoize(P) :-
	asserta((P :- (mark(P), P, asserta(P)))).

mark(P) :-
	P =.. L,
	get_var(L, X),
	\+get_attr(X, memoize, _),
	put_attr(X, memoize, memoize).

get_var([X | _], X) :-
	var(X), !.
get_var([_ | L], X) :-
	get_var(L, X).

attr_unify_hook(memoize, _).

mark/1再帰呼び出しかどうかを示すマークを変数に付けます.
既にマークが付いていたら失敗します.つまりバックトラックを引き起こします.
get_var は述語の引数から変数を探します.変数がなければ失敗します.つまりバックトラックを引き起こします.
これがあるので,fib(1, 5) みたいな呼び出しをした場合にはメモ化しようとしないで値を計算しようとします.もし間違った値をメモ化しちゃダメだからね.
ともあれ (JW),使ってみます.

3 ?- memoize(fib(N, F)).

N = _G296
F = _G297 

Yes
4 ?- fib(10, F).

F = 89 

Yes
5 ?- trace.

Yes
[trace] 5 ?- fib(11, F).
   Call: (7) fib(11, _G430) ? creep
   Call: (8) mark(fib(11, _G430)) ? creep
   Call: (9) fib(11, _G430)=.._L203 ? creep
   Exit: (9) fib(11, _G430)=..[fib, 11, _G430] ? creep
   Call: (9) get_var([fib, 11, _G430], _L204) ? creep
   Call: (10) var(fib) ? creep
   Fail: (10) var(fib) ? creep
   Redo: (9) get_var([fib, 11, _G430], _L204) ? creep
   Call: (10) get_var([11, _G430], _L204) ? creep
   Call: (11) var(11) ? creep
   Fail: (11) var(11) ? creep
   Redo: (10) get_var([11, _G430], _L204) ? creep
   Call: (11) get_var([_G430], _L204) ? creep
   Call: (12) var(_G430) ? creep
   Exit: (12) var(_G430) ? creep
   Exit: (11) get_var([_G430], _G430) ? creep
   Exit: (10) get_var([11, _G430], _G430) ? creep
   Exit: (9) get_var([fib, 11, _G430], _G430) ? creep
   Call: (9) get_attr(_G430, memoize, _L230) ? creep
   Fail: (9) get_attr(_G430, memoize, _L230) ? creep
   Call: (9) put_attr(_G430, memoize, memoize) ? creep
   Exit: (9) put_attr(_G430{memoize = ...}, memoize, memoize) ? creep
   Exit: (8) mark(fib(11, _G430{memoize = ...})) ? creep
   Call: (8) fib(11, _G430{memoize = ...}) ? creep
   Call: (9) mark(fib(11, _G430{memoize = ...})) ? creep
   Call: (10) fib(11, _G430{memoize = ...})=.._L220 ? creep
   Exit: (10) fib(11, _G430{memoize = ...})=..[fib, 11, _G430{memoize = ...}] ? creep
   Call: (10) get_var([fib, 11, _G430{memoize = ...}], _L221) ? creep
   Call: (11) var(fib) ? creep
   Fail: (11) var(fib) ? creep
   Redo: (10) get_var([fib, 11, _G430{memoize = ...}], _L221) ? creep
   Call: (11) get_var([11, _G430{memoize = ...}], _L221) ? creep
   Call: (12) var(11) ? creep
   Fail: (12) var(11) ? creep
   Redo: (11) get_var([11, _G430{memoize = ...}], _L221) ? creep
   Call: (12) get_var([_G430{memoize = ...}], _L221) ? creep
   Call: (13) var(_G430{memoize = ...}) ? creep
   Exit: (13) var(_G430{memoize = ...}) ? creep
   Exit: (12) get_var([_G430{memoize = ...}], _G430{memoize = ...}) ? creep
   Exit: (11) get_var([11, _G430{memoize = ...}], _G430{memoize = ...}) ? creep
   Exit: (10) get_var([fib, 11, _G430{memoize = ...}], _G430{memoize = ...}) ? creep
   Call: (10) get_attr(_G430{memoize = ...}, memoize, _L247) ? creep
   Exit: (10) get_attr(_G430{memoize = ...}, memoize, memoize) ? creep
   Fail: (9) mark(fib(11, _G430{memoize = ...})) ? creep
   Redo: (8) fib(11, _G430{memoize = ...}) ? creep
^  Call: (9) _L204 is 11-1 ? creep
^  Exit: (9) 10 is 11-1 ? creep
^  Call: (9) _L205 is 11-2 ? creep
^  Exit: (9) 9 is 11-2 ? creep
   Call: (9) fib(10, _L206) ? creep
   Exit: (9) fib(10, 89) ? creep
   Call: (9) fib(9, _L207) ? creep
   Exit: (9) fib(9, 55) ? creep
^  Call: (9) _G430{memoize = ...}is 89+55 ? creep
wakeup(att(memoize, memoize, ), 144, )
Saved wakeup to 024F8418
^  Exit: (9) 144 is 89+55 ? creep
Restore wakeup from 024F8418
wakeup(att(memoize, memoize, ), 144, )
   Call: (13) attr_unify_hook(memoize, 144) ? creep
   Exit: (13) attr_unify_hook(memoize, 144) ? creep
^  Exit: (9) 144 is 89+55 ? creep
   Exit: (8) fib(11, 144) ? creep
^  Call: (8) asserta(fib(11, 144)) ? creep
^  Exit: (8) asserta(fib(11, 144)) ? creep
   Exit: (7) fib(11, 144) ? creep

F = 144 

Yes

ちょっとトレースが長くて見にくいですが,青字になっているところが再帰呼び出しされたメモ化する節で最後は失敗してます.
赤字になっているところが値を計算している節で,きちんと結果を求めています.
その中で,fib(10, F).fib(9, F). の呼び出しが計算をすることなく求まっていることが分かります.
つまり,元の述語 fib/2 に手を加えることなくメモ化すると同時に,それ自身もメモ化された述語を利用することができるようになりました.
「手を加えることなく」というのはソースにメモ化の対応をする必要がないという意味で,Prolog データベース的には思い切り,しかも直接手を加えていますけど.
そんなわけで (どんなわけで?),あんまり格好良くないけど,まぁいいや.


そのうち不動点関数とやらが理解できたらそれにも挑戦したいと思います.
いつの日か...


Sampdoria 2 - 2 Inter

(;_;)
今季リーグ戦では初のドロー.
監督がいなくて,Veron がいなくて,Materazzi もいなくて,J.Zanetti も間に合わなかったとなると,敵地でこの相手に引き分けは悪くない...
でもなぁ,すでに 3 敗もしてるわけで...
とりあえず,今季も序盤からずるずる引き離されていくのがなんとも.(ToT)


CanCam 12 月号 エビちゃんベストセレクション 8

CanCam2005年12月号の蛯原友里ちゃん

CanCam から,お気に入りの蛯原友里ちゃんを紹介しようというこのコーナー.
今日は Private Label とのタイアップ「from Private Label, The Epilogue of Princess Elegance」から P172 の友里ちゃん.
ちょっと葉子っぽい? (^^;
この画像で見るといつもより面長っぽく見えますね.
誌面で見るとそんな感じはあまりなくて,キリリとした表情がいい感じなんですが.
そんなわけで (どんなわけで?),やっぱり CanCam 買うしか!!


昨日は...

写経した後ベッドでゴロゴロしていたらそのまま寝てしまいました...
心より恥じる.
そんなわけで (どんなわけで?),エビちゃんベストセレクションをアップしていなかったので今更ながら更新.
いつもより約 12 時間遅れ.
心より恥じる.

Prolog 写経記 その 90 repeat/1

(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.

Prologユーティリティライブラリ

Prologユーティリティライブラリ

今日は repeat/1 を写経します.
順番的には for/1 なのですが,その中で repeat/1 を使ってるのだ.

解説

P が満足されるまで Q を実行する repeat Q until P を実現する.QP は両方共に Prolog のゴールである.

ふむ.

モード

repeat(+)

ふむ.

定義

では,こいつの定義を写経しませう.

:- op(900, fx, repeat).
:- op(850, xfx, until).
repeat Q until P :-
	repeat,
	call((Q, !)),
	call(P).

すっかりお馴染みのオペレータ宣言に,随分シンプルな述語の本体.
ちょっと引っかかるのは述語本体の

	call((Q, !)),

でしょうか.ここには「Q についてのバックトラックはない」という注釈が付いています.
さらに,「注記」に重要なことが書いてあるのでそちらを先に.

注記

repeat/1P が失敗したとき Q についてのバックトラックをせず,むしろ Q を再度呼び出す.repeat/1とみ込み述語の repeat/0 を取り違えないように.

ふむ.
つ・ま・り
述語の定義にある

	call((Q, !)),

のカットがないと,繰り返しの終了条件 P が失敗する度に,つまり繰り返しの度に繰り返しの本体 Q がバックトラックされちゃうということですね.
それは困るのでカットが必要,と.なるほど.

では使用例を写経しませう.

get_filename(F) :-
	repeat (
		write('Filename?'),
		read(F)
	)
	until exists_file(F).

これまた対話的ですね.
これの使用例はないので適当に試すべし.

2 ?- get_filename(F).
Filename?'util.swi'.

F = 'util.swi' 

Yes
3 ?- get_filename(F).
Filename?notfound.
Filename?'memo.swi'.

F = 'memo.swi' 

Yes

ふむ.


Prolog 写経記 その 91 for/1

(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.

Prologユーティリティライブラリ

Prologユーティリティライブラリ

お次は repeat/1 を写経します.

解説

for ループとしての for Count :- I1 (down)to I2 do Q を実現する.ここで,Count はカウンタの名前,I1I2 はそれぞれカウンタの初期値と最終値であり,QProlog のゴールの並びである.

ふむ.
カウントアップとカウントダウンの両方に対応しているらしい.
でもでも,カウントの刻みは指定できないのね.まぁいらないけど.

モード

for(+)

ふむ.

定義

では,こいつの定義を写経しませう.

:- op(900, fx, for).
:- op(850, xfx, to).
:- op(850, xfx, downto).
:- op(800, xfx, do).
:- op(750, xfx, ':=').
for _ := I1 to I2 do _ :-
	I1 > I2, !.
for Count := I1 to I2 do Q :-
	counter_set(Count, I1),
	repeat (Q, counter_inc(Count, I))
		until I = I2, !.
for _ := I1 downto I2 do _ :-
	I1 < I2, !.
for Count := I1 downto I2 do Q :-
	counter_set(Count, I1),
	repeat (Q, counter_dec(Count, I))
		until I = I2, !.

本そのままだと警告がでたので一部修正してます.
はじめの二つはカウントアップ,残りの二つはカウントダウンするためのものですね.
ほとんど同じなのでカウントアップだけ見れば十分っぽい.

for _ := I1 to I2 do _ :-
	I1 > I2, !.
for Count := I1 to I2 do Q :-
	counter_set(Count, I1),
	repeat (Q, counter_inc(Count, I)) until I = I2, !.

最初の節は停止条件なので,実質的には二つめの節だけ.
そこでは以前「5 章 カウンタ」で作成した述語を使って再帰しています.
パッと見たとき長くてビビりましたが意外と簡単♪

注記

for/1 を使用するには,カウンタ管理述語 (5 章参照) と repeat/1 がデータベース中になければならない.手続き的プログラミング言語と比べて,Count は現在の値をためておく変数ではなく,その下にカウンタの値が記録されているキーとしての役割を持つ.

ふむ.

では使用例を写経しませう.

4 ?- for loop_count := 512 downto 509 do
(counter_is(loop_count, I), write(I), tab(2)).
512 511 510 509 I = 509 Yes

ふむ.
これで「10 章 手続き的構造の導入」も終了.


Prolog 写経記 その 92 edit/1

(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.

Prologユーティリティライブラリ

Prologユーティリティライブラリ

続けて「11 章 Prolog インタプリタからのプログラム編集」へ進みます.
まずは edit/1 を写経します.

解説

edit(File) は特定の Prolog ソースファイルを編集するためにテキストプロセッサを呼び出す.テキストプロセッサの処理が終了した後,ファイルはリコンサルトされる.

ふむ.
今時のマルチタスクというかマルチウィンドウな環境で必要かは疑問ですが...
っていうか,Windoes 上の SWI-Prolog でちゃんと動くのかも疑問ですが...

モード

edit(+)

ふむ.

定義

では,こいつの定義を写経しませう.

edit(File) :-
	update_filename(File),
	name(File, Ascii),
	conc("EDT ", Ascii, CommandL),
	name(Command, CommandL),
	unix(system(Command)),
	reconsult(File), !.

update_filename(File) :-
	retract(editing(_)),
	!,
	asserta(editing(File)).
update_filename(File) :-
	asserta(editing(File)).

うーみゅ...
やってることはコマンドライン文字列を組み立てて,組み込み述語を使ってそれを実行してるだけ.
しかし... Windows で動くのか?

注記

edit/1オペレーティングシステムとのインタフェースを提供する,多くの Prolog インタープリタで利用できる.上記の定義においてはオペレーティングシステムのコマンドは system/1 によって実行されるが,Prolog システムによっては shell/1 となっている.
オペレーティングシステムのコマンドはエディタの呼び出しとファイル名を結合した (1 章 conc/3 参照) 文字列 'EDT file_name' である.EDT のところに通常使用しているテキストプロセッサの名前を代入すればよい.ただし,名前の後にファイル名との区切りとして空白を加えておくこと.

そうですか,その辺を調整すればエディタが動くはずですか...
そんなわけで (どんなわけで?),次のように修正しました.

	conc("edlin ", Ascii, CommandL),

Windows っていうか DOS 時代の標準エディタですが,他に思いつかなかったので.心より恥じる.

では使用例を写経しませう.

15 ?- edit('dl.swi').
ERROR: Undefined procedure: reconsult/1

おぉっ!?
コマンドプロンプトが開きましたよ!!
でもでも,エディタを閉じたら上記のエラー.
どうやら,SWI-Prolog には reconsult/1 がないらしい.
っていうか,今時の Prolog にはないらしい.
古い本によると,consult/1reconsult/1 の違いは,Prolog データベースにすでに存在する述語について,consult/1 が節を単に追加するのに対して,reconsult/1 は既存の節を削除して新たに追加する,つまり述語単位で上書きをするところが違っていたようです.
しかし,今時の Prolog はファイルが異なると述語単位で置き換えるのが普通っぽい?
SWI-Prolog はそんな感じ.
そんなわけで (どんなわけで?),

	consult(File), !.

に変更して実行.

17 ?- edit('dl.swi').
% dl.swi compiled 0.00 sec, 1,724 bytes

Yes

\(^o^)/


Prolog 写経記 その 93 edit/0

(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.

Prologユーティリティライブラリ

Prologユーティリティライブラリ

続けて edit/0 を写経します.

解説

editedit/1 によって作られた Prolog ソースファイルを編集するために,ユーザが定義したテキストプロセッサを呼び出す.テキストプロセッサの処理が終了した後,ファイルはリコンサルトされる.

ふむ.
これ,直前に edit/1 で編集したファイルを再度編集するためのもの,ですね.

定義

では,こいつの定義を写経しませう.

edit :-
	editing(File), !,
	name(File, Ascii),
	conc("vi", Ascii, CommandL),
	name(Command, CommandL),
	unix(system(Command)),
	reconsult(File).
edit :-
	write('File not specified, use edit/1'),
	nl.

ココでの数少ないポイントは初っぱなの

	editing(File), !,

ですね.
これ,先ほどの edit/1 で出てくる下請け述語

update_filename(File) :-
	retract(editing(_)),
	!,
	asserta(editing(File)).
update_filename(File) :-
	asserta(editing(File)).

ココで asserta/1 によって動的に登録されている述語です.
edit/1 を実行するたびに,その時のファイル名を Prolog データベースに登録しているわけね.


ともあれ (JW),例によって reconsult/1 はないので consult/1 に変更しておきます.
それから,使用するエディタが vi になっているので edlin にします.

注記

edit/1 の注記を参照のこと.

らじゃあ.

では使用例を写経しませう.
...
が,例が掲載されていない...
まぁいいや.これくらい書いてなくても使えます.

23 ?- edit.

No

むぅ?
どういうことよっ!?
...
調べたところ,以下の問題だと判明.

	conc("vi", Ascii, CommandL),

conc/1 の第 1 引数なんですが,コマンド名の後ろにスペースがないのでファイル名とくっついちゃいます.(^^;
そんなわけで (どんなわけで?),空白を加えて再度お試し.

30 ?- edit.
% dl.swi compiled 0.00 sec, 0 bytes

Yes

\(^o^)/
ちなみに,SWI-Prolog を再起動していきなり edit/0 を実行すると...

2 ?- edit.
ERROR: Undefined procedure: editing/1
   Exception: (7) edit ? creep

...
昔の Prolog は述語が登録されていない場合でも普通に失敗してバックトラックしていたのでしょうか?
今時のというか SWI-Prolog では例外が飛んでバックトラックしないので,2 番目の節は役に立ちませんね.
まぁいいや.


ともあれ (JW).


そんなわけで (どんなわけで?).


ついについに,「Prolog ユーティリティライブラリ」の写経完了です!! \(^o^)/
予定よりも早かったなぁ.何度かまとめて一気に消化したりしたからなぁ.
これで Prolog に慣れたかというと微妙ですが,ほぼ毎日 Prolog する動機にはなりましたね.
問題は,これで日記のレギュラーコーナーが終わってしまうこと.
また技術ネタのない日記に逆戻りだぜ (苦笑).