ピタゴラ三角、完成
inside nprolog
(gdb) p (sizeof(cell)) $1 = 36 (gdb) set print pretty (gdb) p heap[256] $7 = { val = { fltnum = 1.8780421699300832e-315, lngnum = 380119967, { car = { intnum = 380119967, subr = 0x16a82b9f <b_nano>, port = 0x16a82b9f <b_nano>, dyna_vec = 0x16a82b9f <b_nano> }, cdr = { intnum = 0 } } }, aux = 7, var = 0, arity = 0, tag = 6 '\006', flag = FRE, name = 0x7963c4c0 "edit", option = 0 '\000' }
ご本尊様のサイズは36Byte(@32Bit)。256番地には、どうやらprologの述語で言うeditが定義されてる。C語の世界ではそれを担当してるのは、 b_nano
って関数だ。
vbox$ nm npl | fgrep ' B ' | sort 200034d0 B proof : 20003548 B ed_indent 200035c0 B heap 35755fc0 B cell_hash_table 35756180 B variant 37d7bb80 B stack 3814c480 B record_hash_table :
何と言ってもheapは巨大だ! データベースだからね。
それじゃ、64Bitのマシンではどうなる? debianで確認。上記のようにnmコマンドを使って、BSSに割り付けられている変数のアドレスを採取する。
0000000002668480 B heap 0000000023c77080 B column
64Bitって、桁数が多く表示されるので、正直嫌い。
sakae@pen:/tmp/nprolog$ irb irb(main):001:0> (0x23c77080 - 0x2668480) / 10000000 => 56
後は、rubyとかで、変数のアドレス(16進数に注意)の差を取ってから、配列のエレメント数で割れば良い。32Bitシステムよりゆったり使っている事が分かる。
16進の計算にrubyを使ってしまったければ、オイラーのお勧めはgdbだ。簡単に進数の変換とか、混合演算が出来るので、お勧めである。一家に一台、gdb計算器を、だ。
リベンジのピタゴラ三角
前回失敗した、ピタゴラスの三角形を発見するやつ、再チャレンジする。 写経と言うか手本にするのは、example/fizzbuzz.pl だ。
再帰は、大きな数をだんだんと減算してって、0になったら終わりってパターンしかやってなかったので、fizzbuzzみたいに、0から数え上げて行くってのが新鮮だった。
ある数に達したら終了ってのは、効率悪いんじゃねぇ? いちいtある数をロードしてきて判定しなきゃならん。その点0との比較なら、それなりのマシン語で一発比較出来る。なんとまあ、みみっちい考えだ事。もっとおおらかに行け。
pp(A,B,C) :- A^2 + B^2 =:= C^2, write(A), write(', '), write(B), write(', '), write(C), nl. pp(A,B,C). pa(A,B,C) :- A =:= B. pa(A,B,C) :- pp(A,B,C), N is A + 1, pa(N,B,C). pb(A,B,C) :- B =:= C. pb(A,B,C) :- pa(A,B,C), N is B + 1, pb(1,N,C). pc(A,B,C,Max) :- C > Max. pc(A,B,C,Max) :- pb(1,1,C), N is C + 2, pc(1,1,N,Max). run(Max) :- pc(1,1,1,Max).
こうして、分解しちゃうのは楽でいいな。paとかのaは、A辺をスキャンするよって意味。だからA辺だけの事を考えるだけで良い。再帰の終了もB辺と等しくなった時って事で、考えるのが不要とも思えるような楽さ。
ユーザーが与える値は、runの引数だ。後は佳きに計らえって事。
ちょいと頭を使った所は、ピタゴラ三角が成立してるか判定してるppって言う述語。成立してたら結果表示。成立してなかったら? 一見無駄にも思える頭部だけの宣言。これを入れておかないと、思うように動かない。どうなるかは、試してミレー。
すみません、嘘書いてました。サッとは行かなかったんです。で、取った方法は、いわゆるビルドアップ方式。最初ppを書いて、それを手動テスト。pp(1,1,1)とかpp(1,2,3)とかpp(3,4,5)で、副作用が発生して、結果を表示するか(あるいは、しないか)確認。
次はpaを書いてpa(1,2,3)とかpa(1,4,5)とか。これがOkならpbを書いて、試してって具合です。いわゆる、急がば回れですね。
ob$ ./npl -c p.pl N-Prolog Ver 1.42 ?- run(5). 3, 4, 5 yes ?- run(20). 3, 4, 5 5, 12, 13 9, 12, 15 8, 15, 17 yes
よっしゃ、次は、
?- run(100). 3, 4, 5 : 9, 40, 41 27, 36, 45 Segmentation fault (core dumped)
欲張り過ぎて臍まげた? 河岸を変えて、gprologで試してみます。
GNU Prolog 1.4.5 (64 bits) Compiled Sep 30 2020, 15:49:24 with cc By Daniel Diaz Copyright (C) 1999-2020 Daniel Diaz | ?- ['p.pl']. compiling /tmp/nprolog/p.pl for byte code... /tmp/nprolog/p.pl:3: warning: singleton variables [A,B,C] for pp/3 /tmp/nprolog/p.pl:5: warning: singleton variables [C] for pa/3 /tmp/nprolog/p.pl:8: warning: singleton variables [A] for pb/3 /tmp/nprolog/p.pl:11: warning: singleton variables [A,B] for pc/4 /tmp/nprolog/p.pl:12: warning: singleton variables [A,B] for pc/4 /tmp/nprolog/p.pl compiled, 14 lines read - 3020 bytes written, 35 ms yes
盛大に警告が出て来た。ちゃんと与えられた引数を使ってないけど、大丈夫かあ?って奴だ。 haskellだと厳格だから、ちゃんと対処せにゃならんだろうけど、取り合えず先に進む。
| ?- run(100). 3, 4, 5 5, 12, 13 : 35, 84, 91 57, 76, 95 65, 72, 97 true ? (10 ms) yes
何の苦もなく、完走してくれた。見つけ出した3つ組も、C語でのスキャンと一致してたよ。 ロジックの間違いは無いと思っていいだろう。まて、セカンドオピニオンで、最近ご無沙汰してるFreeBSDでのswiplでも確認するか。
Welcome to SWI-Prolog (threaded, 64 bits, version 8.2.0) : ?- ['p.pl']. Warning: /tmp/p.pl:3: Warning: Singleton variables: [A,B,C] Warning: /tmp/p.pl:5: Warning: Singleton variables: [C] Warning: /tmp/p.pl:8: Warning: Singleton variables: [A] Warning: /tmp/p.pl:11: Warning: Singleton variables: [A,B] Warning: /tmp/p.pl:12: Warning: Singleton variables: [A,B] true.
やっぱり警告出て来たね。肝心な結果は、gprologと一致した。よかったよかった。
いや、良くない、検視作業が残ってるだろう。
coredumpの結果を検視する
npl.h
#define VERSION 1.42 #define CELLSIZE 10000000 // small #define HEAPSIZE 7000000 // small #define FREESIZE 500 #define STACKSIZE 1000000 #define VARIANTSIZE 10000000 // small
makefile (関係しそうな部分のみ)
CC = gcc CFLAGS = $(INCS) -Wall -g3 -O0 -gdwarf-2
さあ、いよいよ検視の開始。OpenBSD 6.8 は、意外に保守的なgdbを使っています。kernel開発者には、これで十分なのでしょう。ってか、そもそもgdbなんて使わない?
ob$ gdb npl npl.core GNU gdb 6.3 : #0 singlep (addr=Cannot access memory at address 0x7f7fffbecfe4 ) at data.c:489 489 int singlep(int addr){
ここに到達するまでの軌跡を表示してみます
(gdb) bt ) at data.c:489 #1 0x00000d65ec2c9c5a in anoymousp (addr=7125) at data.c:710 #2 0x00000d65ec2aa7d1 in walpha_conversion (x=7125) at main.c:873 #3 0x00000d65ec2aaac7 in walpha_conversion (x=26903) at main.c:904 #4 0x00000d65ec2aaab6 in walpha_conversion (x=26904) at main.c:904 #5 0x00000d65ec2aaab6 in walpha_conversion (x=26905) at main.c:904 #6 0x00000d65ec2aaa35 in walpha_conversion (x=26906) at main.c:898 #7 0x00000d65ec2aa909 in walpha_conversion (x=26924) at main.c:885 #8 0x00000d65ec2a9665 in prove (goal=7467550, bindings=59587, rest=7466895, n=17019) at main.c:470 #9 0x00000d65ec2a928c in prove_all (goals=7467526, bindings=59587, n=17019) at main.c:385 #10 0x00000d65ec2a9469 in prove (goal=7467546, bindings=59586, rest=7467526, n=17019) at main.c:429 #11 0x00000d65ec2a928c in prove_all (goals=7467529, bindings=59586, n=17019) at main.c:385 :
永遠と続いているって事は、メモリーを使い果たしたんだろうね。
(gdb) info registers rax 0x0 0 rbx 0x0 0 rcx 0xd65ee904900 14731445291264 rdx 0x348b8 215224 rsi 0xd65ee904900 14731445291264 rdi 0x1bd5 7125 rbp 0x7f7fffbed020 0x7f7fffbed020 rsp 0x7f7fffbed000 0x7f7fffbed000 r8 0x101010101010101 72340172838076673 r9 0x8080808080808080 -9187201950435737472 r10 0x0 0 r11 0x99c3c9a1f2f3aa71 -7366822868045026703 r12 0x71f221 7467553 r13 0xd6823d3a9c0 14740928833984 r14 0x7f7ffffeba98 140187732458136 r15 0x3 3 rip 0xd65ec2c924a 0xd65ec2c924a <singlep> eflags 0x10297 66199 cs 0x2b 43 ss 0x23 35 ds 0x23 35 es 0x23 35 fs 0x23 35 gs 0x23 35
こんな物出してどうする?いや、ripにそれらしいのが出てるな。
debian:nprolog$ ./npl N-Prolog Ver 1.46 ?- ['p.pl']. yes ?- run(100). : 9, 40, 41 27, 36, 45 Resource error
なお、debian(32Bit)で、gitしてきたものをそのまま実行したら、ちゃんと止まってくれた。 この違いは何よ?
思うに、gcあたりに何か隠れていそうだな。制限をきつくしてストレス試験ってのは、良い試験と思うぞ。
医学界でも、ストレス試験を行う場合がある。昔、女房が、心臓ドキドキするって主訴で医者にかかった。心電図とか取っても異常なし。で、医者の取った方法は?
携帯用の心拍計をつけて、数日生活してみてくださいってやつ。診察台に横たわっての計測じゃ、良い状態での検査にしかならない。普通の生活中で現象が出現するか確認したい。真っ当な方法だと思うぞ。
後は、半導体とかの加速試験ね。定格を外れる状態で試験して、ウィークポイントを炙り出す。 普通の状態じゃ、中々症状出ないからね。
それにしても、100x99x98回の宝探しで、根を上げるのは、メモリー食べ過ぎ症候群?(と、外野席が、騒いでます)
そんな事より、お前はどうだ。偉そうにしてんじゃねぇ! 反省しろ。
敗軍の将、多くを語る
前回書いた、ピタゴラ三角の探索は失敗作だった。失敗分析をしておく。
C語のループをそのままprologで実現しようとして無理した。一つの述語で、何から何まで実現しようってのは、愚の骨頂。
その無理の主因は、三角を見つけた後、failさせて、次を探させた事。これは100歩譲って良しとしても、バックトラックを招いてしまった。
awk(おおく)じゃ無かったけど、上記に尽きるな。
失敗は成功の基。大いに失敗して、そこから学ぼう。それが強くなる秘訣。
inc/dec
nprologのマニュアルを見ていたら、便利な述語を発見。nprolog独自のインプリメントだから swiplとかとは互換性が無いからその積りで。
?- In=3, inc(In,Out). In = 3 Out = 4 . yes
これの何が嬉しいかと言うと
?- listing : pa(A,B,C) :- pp(A,B,C), inc(A,N), pa(N,B,C). pb(A,B,C) :- B=:=C. pb(A,B,C) :- pa(A,B,C), N is B+1, pb(1,N,C).
pbの所で数値をインクリメントするのに、isを使ってる。はなはだしく違和感があるぞ(オイラーの感想)。それで、paの所ではincを使ってみた(周囲に溶け込んでいて違和感、全く無し!)。
さりげなくlistingなんてのを使ってみた。これ、言うなれば(宣言とルール)データベースのダンプね。lispとかだと、こうはいかないので、なかなか楽しい機能だ。
このincとdecぐらいなら、簡単にswiplに移植出来るよ、単にマクロだろ。本当か?
?- dec(5,Q). % (1) Q = 4 . yes ?- dec(Q,7). % (2) Q = 8 . yes ?- dec(8,7). % (3) yes ?- dec(8,6). % (4) no ?- dec(X,Y). % (5) Instantation error dec #[X,Y]
お前が想定したのは、用例(1)だろう。(2)は、想像通り何をマイナス1したら、7になりますかだ。(3)は、8をマイナス1したら7、は正しいですか? へいその通り。(4)は、間違った演算はnoって答えてる。そして最後は、そんなの神でも分からないんで、エラーだよと、回答拒否。
これだけの機能をきちんと実装しないと、prologの述語としては失格だ。移植なんて大変だぞ。 やるなら、心してかかれ。 上の例だと、端子が2つなんで、組み合わせは4種だけど、端子数が増えると、大変な事は、保障しますよ。
オイラーは元ハード屋。かの昔のICは、端子数ってか足の数をケチる為、入出力共通って構成が多かった。IOコモンってやつね。prologにもそういう考え方が有ってもよさそう。 述語を呼ぶ時は、引数に入力データをセットして渡す。述語が実行されたら、結果は引数に格納されてる。どう、端子数が減って扱いが楽になると思うんだけど、どうよ。
そんなに、入出力端子を設けるって、何か利点はあるの? prologは論理を扱うのが主体。 yes/no でもいいし、true/false でもいいし、白/黒でもいいんだけど、ようするにどっちかってのが非常に大事。
問い合わせをした結果をprologシステムが容易に知る必要がある。問い合わせの述語の結果を返値として、いの一番に採用したら、述語(ようする関数)の計算結果を返す端子が別に必要。 それで、出力端子を引数に任せてしまった訳だ。
C語とかではどうなっているか、ぷち確認。代表例で、sqrt
double sqrt(double x); ERRORS Domain error: x less than -0 errno is set to EDOM. An invalid floating-point exception (FE_INVALID) is raised.
平方根を計算したい時は、引数xにその値を入れてから、sqrtを呼び出す。sqrtの返り値は、計算された値になる。ここまでは良い。じゃ、引数がマイナスだったら? 計算不能。それをどうやって伝える? システムが用意したerrorって言うグローバル変数に入れて返す。
だから、使う時は、エラーになってないか確認して、問題なかったら計算結果を採用する。ちょいと面倒な訳だ。
prologは、述語を連鎖させて、結果がyesの場合は、どんどんと次の述語を実行する機構になってる。
head :- func1, func2, ... funcN .
これを、 scheme風に書くと
(define (head) (and (func1) (func2) ... (funcN)))
(注: 両者共、引数は省略した)
prologで、関数の後ろに付いている、カンマは、and結合の意味だ。勿論or結合が欲しい場合もあるだろう。その場合はカンマに変わってセミコロンを使う。じゃ、否定は? もう忘れた(御免)。
FreeBSDにも移植してテストする
FreeBSDは入れてるけど、正直余り使っていない。この際試してみるか。余りいじっていないので、gccの準備はしてない。
[sakae@fb /tmp/nprolog]$ cc -v FreeBSD clang version 10.0.1 (git@github.com:llvm/llvm-project.git llvmorg-10.0.1-0-gef32c611aa2) Target: x86_64-unknown-freebsd12.2 Thread model: posix InstalledDir: /usr/bin
[sakae@fb /tmp/nprolog]$ vi builtin.c main.c
毎回、リナ用の stdio_ext.h
を消すの面倒だな。リナ用の囲いを付けてください。
後は、自己責任にntp.hのFLUSHの所を修正。最後にmakefileで、ccにするのと -ldlの削除か。
cc -Wall -O3 -c main.c -o main.o main.c:1195:13: error: expected expression else if(c == 'b') ^ 1 error generated. *** Error code 1
新種のエラーだな。
1192 #elif __OpenBSD__ || __FreeBSD__ 1193 printf("\a"); 1194 #endif 1195 else if(c == 'b')
c == 'a'の所の文が満たされないのでエラってた。FreeBSDも仲間に入れてあげたよ。
[sakae@fb /tmp/nprolog]$ ./npl -c p.pl N-Prolog Ver 1.46 ?- run(100). 3, 4, 5 : 27, 36, 45 Resource error
メモリーを半分に絞っても、CFLAGSを同じに設定しても、coreは吐かなかった。OS固有の問題なのだろうか?
偶然かも知れないけど、25,36,45の直角三角形を見つけた後、core、あるいはResource errorになってるな。何か匂うぞ、クンクン。
獲物を追って
[sakae@fb /tmp/nprolog]$ gdb npl : (gdb) b main.c:419 Breakpoint 1 at 0x20c914: file main.c, line 419. (gdb) r -c p.pl Starting program: /tmp/nprolog/npl -c p.pl N-Prolog Ver 1.46 ?- run(100). : 27, 36, 45 Breakpoint 1, prove (goal=7494324, bindings=63024, rest=7494373, n=18001) at main.c:419 419 error(RESOURCE_ERR,"",NIL); (gdb) bt : #1122 0x000000000020cd78 in prove (goal=7484278, bindings=61743, rest=7484055, n=17635) at main.c:505 #1123 0x000000000020bd91 in prove_all (goals=7484254, bindings=61743, n=17635) at main.c:396 :
coreとResource Errorは紙一重の違いに思えてきた。スポットで追っても、全体像が見えていないので、意味ないな。後は作者様にバトンタッチ。nは評価の回数?かな。18000回回っても、結果が出ないとSTOPを掛けてるように見受けられるけど。 lispで、evalとapplyを行ったり来たりしながら収束させるけど、それと同じ?