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)

補完出来たものが、そのまま走る訳では無いという、結論かな。


This year's Index

Home