CRUD in nprolog
tests/verify.pl
nprologにverifyが追加された。テストである。これの何が嬉しいかと言うと、各述語とかの用法を知る事が出来るから。
golangを勉強する時にこの恩恵を受けた。尤もgolangの場合はtestってファイルなんだけどね。説明書で意味不でも、簡潔なテストコードを見れば理解出来る。
debian:runtime$ ls malloc* malloc.go malloc_test.go
例えば、nprologのmanual.txtに
□ functor((Struct,Name,Arity) ストラクチャ名とアリティーを戻す。
とだけ、書かれている。ストラクチャ名って、一体何? 名前はまあatomとでも思っておけば間違い無いだろう。アリティーは? 幸いオイラーはlispやってたんで知ってたけど、普通の人には意味不だろう。アリティーってのは、引数の個数を意味する専門用語。
マニュアルの最初に、用語の説明が必要だろうね。で、verifyの例から想像して、後は実験有るのみ。
test(functor) :- verify(functor(book(pooh,miline,aa),book,3)).
一行目のtest(functor)は、テストの項目番号だから無視。二行目のverify関数は、引数の実行結果が真になる事を期待(test ok)、偽だと(test fail).
だから、引数の中だけを、マニュアルとユニフィケーションさせて、それぞれを吟味すればいいんだな。これぞ、人間prologだ。
book(pooh,miline,aa) ストラクチャ名 book name 3 アリティー
ストラクチャ名ってのは、完全な関数の例を言うのね。nameってのは、関数名。アリティーは、想像出来るよね。したら実験してみい。
?- functor(book(pooh,miline,aa),book,2). no ?- functor(book(pooh,aa),book,2). yes
このfunctor手続きって、第二引数のnameって無駄だねぇ? とか思うんだけど、これが意味を持つ場面って有るのかな?
□ aargN,Term,X) Xを項のN番目の値にユニフィケーションする。 (引数は1から増加方向に番号付けられている。)
このTermも、オイラーには、よう分からんの部類。早速実験。
?- arg(2,book(poetry,milne,poetry),A). A = milne . yes ?- arg(2,book(poetry,HOGE,poetry),A). A = HOGE . yes ?- atomic(arg(2,book(poetry,HOGE,poetry),A)). no ?- var(arg(2,book(poetry,HOGE,poetry),A)). no
取り出して来たものの正体は? varだと思うんだけど、本当は何?
?- arg(2,book(poetry,HOGE,poetry),A), var(A). A = HOGE . yes
いかん、いかん、ついlispの癖が出てしまう。ちゃんとprolog脳に切り替えようね。
さらっと書いてあるマニュアルより、実例が載ってるverifyの方が、なんぼか利用度が高いな。 全関数のverifyをお願いします。まて、今のままじゃ、副作用のある物はテスト出来ないぞ。
こういう制限は他の言語のtestでも有るはずで、何度か、この事に注目して、どう解決してるか調べた事が有るんだけど、何時も尻切れトンボなんだよな。ご健闘を願うって、お願いモードです。
CRUD
prologはデータベースだと言う。宣言とルールの塊。そいつに対して、質問をして答えを引き出すのが本業。
データベースって事で、ピンと来た語句が有る。 CRUD とか、 【MySQL入門③】データベースの基本操作CRUDを解説! だ。実はCRUDは、データベースの専売特許では無い。Webで作るアプリケーションなんかでも、同じ考えをする。
かの昔、perlとかrubyで掲示板を作るってのが流行った。今は廃ってしまったけど、2CHとかね。これの作り方を説明したページで、CRUDを実現出来たら、一応一人前ですって言ってたな。 オイラーも、(勝手に)20%ルールで、社内向けのスケジュール板や、技術用の掲示板を作ったものだ。
スケジュール板なんかは、設置が簡単に出来るように、本体はcgi用にプログラムを1本だけ。誰用と言うか、グループ・課・部とかの設定を、テキストファイルで指定しておくだけってのが受けて、あちこちに貰われて行った。懐かしい思い出だ。
nprologでCRUDったら、取り合えず、これでしょう。
?- recordz(fumu,cry(cat,mew),Ref). Ref = 27332 . yes ?- instance(27332, X). X = cry(fumu,cat,mew) . yes
データベースは、検索のスピードが命。ならば、hashにして、O(1)の負担でアクセス出来るようにしようと、誰もが思うはず。で実現した枠組みが上記だそうだ。
□ recorda(Key,Term,Ref) 述語の戦闘に項を加えて、その項の新しい参照番号を戻す。 □ recordz(Key,Term,Ref) 述語の終わりに項を追加し、その項に割り当てられた参照番号を戻す。 □ recordh(Table_name,Sort_key,Term) ハッシュテーブルに項を記録する。 □ ref(X) Xが参照番号かどうかを調べる。 □ removeallh(Table_name) ハッシュテーブルを削除する。 □ removeh(Table_name,Sort_key,Term) ハッシュテーブルから項を削除する。
背景が想像しにくい。CRUDに当てはめると、recordhがCreateに相当、removeallhがDeleteに相当。後はUpdateになるかな。Readは、使うに分類されるんで、陽に表われないって事になるかな。
Sort_key
と、Keyの関係は? 複数の Table_name
を作った場合、その使い分けは? まだ、理解出来ないな。
recordzで登録した時のkeyは、ひょっとして、環境を分離(例えばパッケージと言う概念を取り入れる)する為に使うのかな?
?- cry(fumu,cat,mew). no ?- cry(cat,mew). no ?- listing. yes
なにか、中途半端な感じがする。実例を是非、verifyにさりげなく上げてください。
listing
nprologの普通のCRUDに、取り合えず着目してみる。どんな物が登録されてるかlistingする。広義のRの範疇になるかな。
debian:nprolog$ ./npl -c example/pp.pl N-Prolog Ver 1.62 ?- listing. : my_call(X;Y) :- call(X), !. my_call(X;Y) :- call(Y). yes
起動時に、ファイルからデータを取り込み、データベースを構築。そして内容(を全部)確認。
これを実現してるソースを読むと、 (builtin.c/b_listing)
n = length(arglist); if(n == 0){ : if(n == 1){ :
アリティーが0と引数が1つの場合を許してる。
?- listing(pp). pp :- repeat, : (my_call(X)->write(yes);write(no)), fail. yes
指定したものだけ、listingされた。ここまでで話半分。じゃ、これらのデータは、どうのように保持してるの? ソースをざっと見。
if(n == 0){ list = listreverse(predicates); listing_flag = 1; while(!nullp(list)){ pred = car(list); list = cdr(list); :
predicatesって名前のリストを逆順にして、それを辿りながら表示してる。と言う事は、assertで登録したやつは、リストの最後に登録されるんだな。
?- assert((likes(i,ruby))). yes ?- listing. : my_call(X;Y) :- call(Y). likes(i,ruby). yes
登録現場
predicatesが、DBのデータを保持してる事が分かったので、それを頼りに登録現場を探してみる。
debian:nprolog$ grep predicates *.c builtin.c: listremove(cadr(arg1),predicates); builtin.c: list = listreverse(predicates); builtin.c: predlist = reverse(predicates); data.c: if(!memq(pred,predicates)) data.c: predicates = cons(pred,predicates); data.c: if(!memq(pred,predicates)) data.c: predicates = cons(pred,predicates); gbc.c: markcell(predicates); main.c:int predicates = NIL; main.c: predicates = NIL; main.c: predicates = NIL;
mainでは、何も登録してないよ宣言してる。使ってる所は、先程見た通りbuiltin.cな所だな。 すると、登録現場はdata.cの中だな。
(gdb) b add_data Breakpoint 1 at 0x284b3: file data.c, line 1501. (gdb) b insert_data Breakpoint 2 at 0x285ca: file data.c, line 1520. (gdb) r -c example/pp.pl Starting program: /tmp/nprolog/npl -c example/pp.pl Breakpoint 1, add_data (pred=723, data=916) at data.c:1501 1501 memoize_arity(data,pred);
data.cを見たら、2つの手続きで使ってたので、両方にBPを置く。
(gdb) bt #0 add_data (pred=723, data=916) at data.c:1501 #1 0x0041dbb0 in o_define (x=723, y=896) at builtin.c:3748 #2 0x00403e58 in operate (x=899) at main.c:925 #3 0x00418158 in b_assert (arglist=826, rest=0) at builtin.c:2140 #4 0x00415bc1 in b_reconsult (arglist=721, rest=0) at builtin.c:1420 #5 0x00401999 in main (argc=3, argv=0xbffff4a4) at main.c:238
何時もの足取り調査。これが基本ですよ。このデータから、探索エリアを広げられますからね。
(gdb) p pred $4 = 770 (gdb) call heapdump(770,770) addr F TAG CAR CDR BIND NAME 0000770 FRE ATOM 0001003 0000000 0000006 my_call
gdb起動中は、折角登録して頂いた、heapdも出番がありませんです。まあ、こんな具合に丁寧に足取りを追って行くのです。地道な捜査が、勝利への近道ですかね。
creat
make(ken,nprolog). make(matz,ruby). make(Who,nprolog).
こんなのを読み込ませると、
?- listing. make(ken,nprolog). make(matz,ruby). make(Who,nprolog). yes
こんな風に登録された。
?- make(Qwho,nprolog). Qwho = ken ; Qwho = v_1 ; no
そして、試しに問い合わせしてみると、内部で登録された名前が出て来た。
スクリプト中に、問い合わせを記述しておいて、読み込み時と言うか登録時に、問い合わせ結果を表示するって、出来ない相談なのかな?
[sakae@fb /tmp]$ swipl ?- ['z.pl']. Warning: /tmp/z.pl:3: Warning: Singleton variables: [Who] true. ?- listing. : make(ken, nprolog). make(matz, ruby). make(Who, nprolog).
swiplだと、警告が出て来たな。
?- make(Qwho,nprolog). Qwho = ken ; true.
問い合わせには、答えは一つだけだった。
?- make(Qwho,Lang). Qwho = ken, Lang = nprolog ; Qwho = matz, Lang = ruby ; Lang = nprolog.
亡霊もnprologを作ってますよって、言ってるな。 prolog初心者の戯言です。
Segmentation fault
READ.mdが更新されて、repl上の編集方法が説明されてた。Esc TAB で、補完が出来るとな。
debian:nprolog$ ./npl N-Prolog Ver 1.62 ?- cu %%% Esc TAB 1:current_visible 2:current_predicate 3:current_op ?- current_visible. Segmentation fault
新しい機能は、BUGを炙り出す? 試したものは、何処にも解説が無いから、作りかけ? こういうの、オイラーの大好物。
debian:nprolog$ ulimit -c 1000000 debian:nprolog$ ./npl N-Prolog Ver 1.62 ?- current_visible. Segmentation fault (core dumped)
検視したけど、有益な情報無し。ちゃんとgdbにかけられるようにする。
debian:nprolog$ emacs makefile # -03 -> -O0 -g debian:nprolog$ make gcc -c main.c -o main.o -Wall -O0 -g gcc -c parser.c -o parser.o -Wall -O0 -g parser.c: In function ‘readitem’: parser.c:1299:35: warning: ‘temp’ may be used uninitialized in this function [-Wmaybe-uninitialized] return(temp); ^ gcc -c function.c -o function.o -Wall -O0 -g :
オプティマイズレベルを変えると、隠れていたワーニングも出てくるのね。
debian:nprolog$ gdb -q npl core Reading symbols from npl... [New LWP 7740] Core was generated by `./npl'. Program terminated with signal SIGSEGV, Segmentation fault. #0 0x00000000 in ?? () (gdb) bt #0 0x00000000 in ?? () #1 0x004218e8 in prove (goal=719, bindings=0, rest=180, n=0) at main.c:497 #2 0x00421672 in prove_all (goals=17000003, bindings=0, n=0) at main.c:445 #3 0x00421264 in query (x=719) at main.c:373 #4 0x00420f63 in main (argc=1, argv=0xbfbe2ec4) at main.c:303
まあ、何時ものコースを辿っている訳です。
debian:nprolog$ grep current_visible *.c main.c:{"current_visible"},{"stream_property"},{"between"}, debian:nprolog$ ./npl N-Prolog Ver 1.62 ?- stream_property. Segmentation fault (core dumped)
補完出来たものが、そのまま走る訳では無いという、結論かな。