prolog

『犯罪者プロファイリング入門』なんてのを読んだ。この所、プロファイリングブームですから。 この本、警察庁科学警察研究所の方と科捜研の方の共著になるもの。

この本で、科捜研ってのは、各道府県にある事を恥ずかしながら初めて知りましたよ。 科捜研の女なんて番組は真面目に見てないものですから。

科捜研には、文書鑑定をやる文書係りとか化学係りとか、ポリグラフ(嘘発見器)を担当する 係りとかが有るそうな。勿論、嘘発見器にかける質問票とかは、科捜研の係りの人が捻り出すそうです。

犯罪者プロファイリングにはFBI流とヨーロッパで発達した流派に大きく分かれるとか。 FBI流は、いわゆる職人派。ヨーロッパ流は、現場の捜査官と心理担当のコンビらしい。 日本の警察は、ヨーロッパ流を取り入れてるとの事。

勿論、コンピュータの力も取り入れている。犯罪データベース。これを使って類型の犯罪から いろいろと犯人の輪郭を絞って行く。

データベースにどんなデータが登録されているか? 通り魔DBの例が載ってた。 大きく分けて、被疑者(犯人)情報、犯行場面、被害者情報。被疑者の詳細は、性別、年齢、社会的地位(職業)等。 犯行場面では、場所(路上、屋内、公園等)、制圧(暴力、凶器、恫喝等)、天候等が保管される。 被害者情報は項目として被疑者と同じものが並んでいるようだ。

場所と被害者からマッチする被疑者像を検索するんだな。もう、統計処理ですよ。 統計処理と言っても、大きく分けて2つの方式になるとか。教師つき学習と教師無し学習。 前者では、線形判別学習とか決定木、ニューラルネット等。後者では、主成分分析とか、階層的クラスタリング とか、自己組織マップとかの方法が有るらしい。

こうなると統計言語Rの出番かと思うんだけど、警察に方々にはRなんて認知されていないみたい。 統計ソフトと言えばSPSSでしょうと言う訳。そして、Visual Mining Studioとかを使うらしい。 このマイニングツールって、数理システムの製品なのね。

数理システムと言えば、昔行った事があるぞ。そう、アレグロ 顧問リスプのセミナーが 有った時にね。そして件のソフトの資料を頂いた覚えがある。こういうのには顧問リスプが うってつけなんだろうね。何たって、顧問ですから。。。

まてまて、まだまだあるぞ、prologが。こいつは推論する事にかけちゃ天下一品さ。

elixir

日経Linuxでmatzさんがjulia等と共にelixir なんてのを薦めていたように思うんだけど、これってどうよ? えっ、そっちの方じゃなくて elixir erlang なんて検索語で出て来るelixirですってば。 嗚呼、びっくりしたなあ。まあ、 なぜElixirなのかなんてのが、 歌舞伎座で講演されるぐらいですから。。。やっとかないと今の流行から取り残されますよ。 歌舞伎座の人も頑張ってますよ。

なんだか、これを作った人がerlangってprologっぽくね? 今更prologなんて流行らないよってんで、 rubyに似せて作ったとか。ならば、おいらも首を突っ込んでみるかな。幸い、fedoraにパッケージが 有ったので、直ぐに入ったよ。

Installed:
  elixir.noarch 0:0.12.2-2.fc20

Dependency Installed:
  erlang-asn1.i686 0:R16B-03.1.fc20
  erlang-compiler.i686 0:R16B-03.1.fc20
  erlang-crypto.i686 0:R16B-03.1.fc20
  erlang-erts.i686 0:R16B-03.1.fc20
  erlang-hipe.i686 0:R16B-03.1.fc20
  erlang-inets.i686 0:R16B-03.1.fc20
  erlang-kernel.i686 0:R16B-03.1.fc20
  erlang-mnesia.i686 0:R16B-03.1.fc20
  erlang-public_key.i686 0:R16B-03.1.fc20
  erlang-runtime_tools.i686 0:R16B-03.1.fc20
  erlang-ssl.i686 0:R16B-03.1.fc20
  erlang-stdlib.i686 0:R16B-03.1.fc20
  erlang-syntax_tools.i686 0:R16B-03.1.fc20

ごっそりとerlang陣営を引き連れてきたぞ。で、rubyのirb相当がiexって事で用意されてた。一応、 起動確認をば。

[sakae@fedora ~]$ iex
Erlang R16B03 (erts-5.10.4) [source] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (0.12.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
a

この後何をしたら良いんでしょうか? 作ったスクリプトは、elixirから起動するも良し、 下記のコンパイラを使ってerlangVMのクラスファイルって言うかビームファイルに落として おいて、erlangから起動するも良し、自由にどうぞって訳だな。

[sakae@fedora ~]$ elixirc -h
Usage: elixirc [elixir switches] [compiler switches] [.ex files]

  -o               The directory to output compiled files
  --no-docs        Do not attach documentation to compiled modules
  --no-debug-info  Do not attach debug info to compiled modules
  --ignore-module-conflict
  --warnings-as-errors Treat warnings as errors and return non-zero exit code
  --verbose        Print informational messages.

** Options given after -- are passed down to the executed code
** Options can be passed to the erlang runtime using ELIXIR_ERL_OPTS

何やら外国ではelixir本も出てるようなんで、一定の市民権を獲得してるんでしょうな。 市民権と言えば、スクリプトの中にドキュメントを埋め込む事が推奨されてて、 Documentation as first-class citizenって具合に誇らしげに宣伝してた。

これ、古くは Lispに、新しくはPythonやclojureにも有るね。起動が遅くなってもいいから、ちゃんとドキュメントを 書いておこうよって、文芸的プログラミング推進派。gaucheは、速く起動させる為に、あえて 一級市民には入れなかったとか。人それぞれで面白い。

prolog

上でerlangがprologの影響を受けたって書いたけど、ならば基礎の基礎からだな。ここで 『7つの言語の7つの世界』なんて本を思い出したように取り出してみた。そしたら、erlangも prologの収録されてましたよ。

某板では、こんな発言があるな。

Erlangのオリジナルは論理型言語から出発しているが、
その言語とは(Concurrent PrologやGHCといった)並列論理型言語のことであり、
(いわゆるPrologと呼ばれる)逐次論理型言語が持つバックトラック機能は失われている
だから、逐次Prologに関する知識の多くはErlangでは役に立たないと考えた方がいい
  Erlangの外観が逐次Prolog(いわゆるフツーのPROLOG)と似ているからといって、
惑わされてはいけない.

prologって流行のオブジェクト嗜好とも関数型言語とも違う、独立峰だ。これはもう登って、 経験値を高めておかないとな。かの本をざっと流し読みしたよ。そして、何か適当な本が 無いかと探ってみると、、昔はブームがあって山のように出版されてたようだけど、もうみんな絶版。 手に入らない。で、Webに無いかと探してみると、

Prolog wikipedia

Prologオンラインテキスト

Prolog プログラミング入門

お気楽 Prolog プログラミング入門

大黒先生の、初級Prolog講座

Haskellが数学だとすれば、Prologは国語だ

国語ってのが気に入りましたよ。で、水練じゃつまんないんで、処理系を入れて動かしてみる。 取り合えず、FreeBSD上でね。

gprolog

GNUのやつとswiplが有ったけど、GNUのやつをチョイスしてみた。本山は The GNU Prolog web siteで、経典と言うか Manualが有れば、何とかなるだろう。

入れたはいいけど、どんなコマンドが有るの?

[sakae@fb10 ~]$ pkg info -l gprolog
gprolog-1.4.1:
        /usr/local/bin/fd2c
        /usr/local/bin/gplc
        /usr/local/bin/gprolog
        /usr/local/bin/hexgplc
        /usr/local/bin/ma2asm
        /usr/local/bin/pl2wam
        /usr/local/bin/wam2ma
           :

いろいろあるけど雰囲気からしてgprologってのがメインっぽい。で、 emacsから使いたい場合は、こうするようだ。

(setq auto-mode-alist
      (append '(("\\.pl" . prolog-mode))
       auto-mode-alist))
(setq prolog-program-name "/usr/local/bin/gprolog")
;; (setq prolog-consult-string "[user].\n")

こんな風に書いておくだけで良い。emacsには既にコードが含まれているからね。 簡単な使い方が prolog on emacs 有ったぞ。swlplだけど、一緒だろう。

まずは、7つの言語に習ってと。

[sakae@fb10 ~]$ gprolog
GNU Prolog 1.4.1
By Daniel Diaz
Copyright (C) 1999-2012 Daniel Diaz
| ?- like(sakae, ruby).
uncaught exception: error(existence_error(procedure,like/2),top_level/0)

いきなり質問したって、そんなのprologは知る訳ないわな。普通は、こういう定義を ファイルに書いておいて、それを読み込んで(覚えさせて)から、質問するのが人の道。 でも、せっかちな人用に、抜け道が用意されてる。

| ?- [user].
compiling user for byte code...
like(sakae, ruby).
like(sakae, scheme).
like(matz,  ruby).
like(shiro, scheme).
CTRL-d で、復帰する
user compiled, 5 lines read - 479 bytes written, 97160 ms

yes

userって言う仮想ファイルから読み込みます。終わりは、EOFって事ね。これって、かの昔、 Bill Joyさんが、cat - >hoge.c とかやって、いきなり端末からソースを起したのに 等しい行為です。凡人は慎みましょう。

上のは、まあ意味分かるよね。現実をprologに覚えて貰ったんだ。そしておもむろに

| ?- like(matz, ruby).

yes
| ?- like(matz, scheme).

no

matzさんがrubyを好きなのはうなずけるけど、schemeは嫌いってどうよ? と、早合点しては いけません。noは、否定の意味と、そんな事は知らんって意味が有るんですな。と、かのWeb に説明が有ったけど、両者はどうやって見分けるの? 教えて偉い人。

| ?- like(Who, scheme).

Who = sakae ? ;

Who = shiro ? ;

no

今度は、誰がschemeを好きかって質問。最初の回答の後にセミコロンを入力すると、続いて 検索してくれるよ。

次なる例は例によって、fact。

fact(0, 1).
fact(X, Sum) :-
    X > 0, X1 is X - 1, fact(X1, Sum1), Sum is X * Sum1.

fact(0)の答えは1てすって(再帰の)基底を宣言。それ以外は、次のルールが評価される。 Xが零より大きかったら X1 is X - 1しなさい(普通の言語でX1=X-1と書きたいけど、 prologの場合は、我慢我慢)。間にカンマが入っているけど、これは andね。 そして再帰、

上記をfact.plってファイルにsave。おもむろに、run-prologすると画面が割れて、gplologが 顔を出す。

GNU Prolog 1.4.1
By Daniel Diaz
Copyright (C) 1999-2012 Daniel Diaz
| ?- [fact].
compiling /usr/home/sakae/src/pl/fact.pl for byte code...
/usr/home/sakae/src/pl/fact.pl compiled, 3 lines read - 807 bytes written, 24 m\s

yes
| ?- fact(10,Ans).

Ans = 3628800 ?

yes

これで答えが求まった。間違っても1引数の関数として実行しないように。間違うと

| ?- fact(5).
uncaught exception: error(existence_error(procedure,fact/1),top_level/0)

文句を言われる。システムが、どういう風にコードを認識してるかは

| ?- listing.

% file: /usr/home/sakae/src/pl/fact.pl

fact(0, 1).
fact(A, B) :-
        A > 0,
        C is A - 1,
        fact(C, D),
        B is A * D.

こんな具合に確認出来る。大文字で始まるのは、変数だよ。それ以外はアトムって言うから 分解出来ないっていう建前なんだな。

どんな風に計算が進んで行くかは、traceしてみれば分かる。

| ?- trace.
The debugger will first creep -- showing everything (trace)

yes
{trace}
| ?- fact(2,X).
      1    1  Call: fact(2,_17) ?
      2    2  Call: 2>0 ?
      2    2  Exit: 2>0 ?
      3    2  Call: _114 is 2-1 ?
      3    2  Exit: 1 is 2-1 ?
      4    2  Call: fact(1,_139) ?
      5    3  Call: 1>0 ?
      5    3  Exit: 1>0 ?
      6    3  Call: _191 is 1-1 ?
      6    3  Exit: 0 is 1-1 ?
      7    3  Call: fact(0,_216) ?
      7    3  Exit: fact(0,1) ?
      8    3  Call: _244 is 1*1 ?
      8    3  Exit: 1 is 1*1 ?
      4    2  Exit: fact(1,1) ?
      9    2  Call: _17 is 2*1 ?
      9    2  Exit: 2 is 2*1 ?
      1    1  Exit: fact(2,2) ?

X = 2 ?

yes
{trace}

traceの解除はnotraceだ。他にもspyなんてのが有るぞ。

gplc

gprologはコンパイラーも備わっていて、コードをコンパイルして、実行プログラムを作れるそうだ。 早速やってみる。下記は某Webから頂いてきた。日本人なら誰でも知っている 歳を取らない不思議な家族、サザエさん一家の家系図。

% child(X, Y):X は Y の子供である
child(sazae, namihei).
child(sazae, fune).
child(katsuo, namihei).
child(katsuo, fune).
child(wakame, namihei).
child(wakame, fune).
child(tarao, sazae).
child(tarao, masuo).

% P が成り立てば true を、成り立たなければ fail を X に代入
istrue(P, X) :- P, !, X = true; X = fail.

:- initialization(sazae_family).
sazae_family :-

% masuo は namihei の子供か?
istrue(child(masuo, namihei), X0), write(X0), nl,

% fune の子供を列挙する
findall(X1, child(X1, fune), List1), write(List1), nl,

% tarao を子供にもつ者(つまり、taraoの親)を列挙する
findall(X2, child(tarao, X2), List2), write(List2), nl,

% child(X, Y) が成り立つすべての組 (X, Y) を列挙する
findall([X3, Y3], child(X3, Y3), List3), write(List3), nl.

実行プログラムが自動的に走るようにイニシャライザーの定義を書いておくのと、gprologの replに落ち込まないようにコンパイラーに指示を与える。

[sakae@fb10 ~/src/pl]$ gplc -v sazae.pl --no-top-level
Prolog compiler (GNU Prolog) 1.4.1
By Daniel Diaz
Copyright (C) 1999-2012 Daniel Diaz

GNU Prolog comes with ABSOLUTELY NO WARRANTY.
This is free software; see the source or the file
named COPYING for copying conditions.

Path used: /usr/local/gprolog-1.4.1

*** Compiling

--- file: sazae.pl
pl2wam -o /tmp/gplcqDVwGb.wam sazae.pl
wam2ma -o /tmp/gplcG6GTcd.ma /tmp/gplcqDVwGb.wam
delete /tmp/gplcqDVwGb.wam
ma2asm -o /tmp/gplcWzsgJe.s /tmp/gplcG6GTcd.ma
delete /tmp/gplcG6GTcd.ma
as  -o /tmp/gplc8QYXyb.o /tmp/gplcWzsgJe.s
delete /tmp/gplcWzsgJe.s

*** Linking

gcc46 -march=i486 -m32 -fno-strict-aliasing  -Wl,-rpath=/usr/local/lib/gcc46 -L/usr/local/lib/gcc46 -o sazae /tmp/gplc8QYXyb.o /usr/local/gprolog-1.4.1/lib/all_pl_bips.o /usr/local/gprolog-1.4.1/lib/all_fd_bips.o /usr/local/gprolog-1.4.1/lib/libbips_fd.a /usr/local/gprolog-1.4.1/lib/libengine_fd.a /usr/local/gprolog-1.4.1/lib/libbips_pl.a /usr/local/gprolog-1.4.1/lib/libengine_pl.a /usr/local/gprolog-1.4.1/lib/liblinedit.a -lm
delete /tmp/gplc8QYXyb.o

コンパイルの過程が良く分かるように-vオプションを付けてみた。マシンのアーキテクチャが i486なんて、時代がかった奴でも動くようにしてくれてるのか。

[sakae@fb10 ~/src/pl]$ ./sazae
fail
[sazae,katsuo,wakame]
[sazae,masuo]
[[sazae,namihei],[sazae,fune],[katsuo,namihei],[katsuo,fune],[wakame,namihei],[wakame,fune],[tarao,sazae],[tarao,masuo]]

旨く実行出来た。

[sakae@fb10 ~/src/pl]$ ldd ./sazae
./sazae:
        libm.so.5 => /lib/libm.so.5 (0x28111000)
        libc.so.7 => /lib/libc.so.7 (0x28133000)

いたって平凡なライブラリィーしか使っていない。

WAM

GNU PrologのHP上に、どやって動くのって説明が載ってて、そこにWarren Abstract Machine なんて名前が出てくる。ワーナーさんの考えた機械なんだな。この機械はprologを実行する のに都合よく考えて作ったとな。丁度、Schemeを実行するのに都合が良い機械、SECDマシン が有ったように。

SECDを復習すると、スタック、環境、コード、データって事だったな。そなら、Prologは如何に? ぐぐったら、 (D.Warrenの抽象Prolog命令セット, WAM) なんてのが出てきた。

gprolog from src

FreeBSDでは楽して(手抜きして)、pkgから入れちゃったけど、やはりオイラーの流儀に反する。 という事で、万次郎Linuxにはソースから入れてみた。1分で完了。ログを取ったので、 どんな風にコンパイルしてるか見てみた。

cd EnginePl; make config
(cd ../TopComp; make gplc)
. ./SETVARS;for i in EnginePl TopComp Wam2Ma Ma2Asm Linedit BipsPl Pl2Wam Fd2C EngineFD BipsFD;do (cd $i; make) || exit 1; done;\
(cd TopComp; make top-level) || exit 1;\
(cd Pl2Wam; make stage2)

configして、gplcを作って、コンパイルして、plのソースコードをwamに落としてコンパイルって 手順だな。

折角なのでソースも見とくと、上で出てきたWAMの仕様が、EnginePl/wam_*.h あたりで出ていた。 そのうちの、wam_archi.hに使うRegの定義が出てたぞ。

#define Reg(i)                  (((i)==0) ? (WamWord) TR        : \
                                 ((i)==1) ? (WamWord) B         : \
                                 ((i)==2) ? (WamWord) H         : \
                                 ((i)==3) ? (WamWord) HB1       : \
                                 ((i)==4) ? (WamWord) CP        : \
                                 ((i)==5) ? (WamWord) E         : \
                                 ((i)==6) ? (WamWord) CS        : \
                                 ((i)==7) ? (WamWord) S         : \
                                 ((i)==8) ? (WamWord) STAMP     : \
                                 ((i)==9) ? (WamWord) BCI       : \
                                            (WamWord) LSSA)

そんじゃ、Linuxでもコンパイルテストをしてみる。今度は、コンパイラードライバーに 中間ファイルを消すなオプションを付けておいた。

[sakae@manjaro pl]$ gplc  --no-del-temp-files -v sazae.pl
Prolog compiler (GNU Prolog) 1.4.4
By Daniel Diaz
Copyright (C) 1999-2013 Daniel Diaz

GNU Prolog comes with ABSOLUTELY NO WARRANTY.
This is free software; see the source or the file
named COPYING for copying conditions.

Path used: /usr/local/gprolog-1.4.4

*** Compiling

--- file: sazae.pl
pl2wam -o /tmp/gplc7j3wGb.wam sazae.pl
wam2ma -o /tmp/gplc4tWTcd.ma /tmp/gplc7j3wGb.wam
ma2asm -o /tmp/gplc1DPgJe.s /tmp/gplc4tWTcd.ma
as --32 -o /tmp/gplcUBtYyb.o /tmp/gplc1DPgJe.s

*** Linking

gcc -march=pentiumpro -m32 -fno-strict-aliasing  -o sazae /tmp/gplcUBtYyb.o /usr/local/gprolog-1.4.4/lib/all_pl_bips.o /usr/local/gprolog-1.4.4/lib/all_fd_bips.o /usr/local/gprolog-1.4.4/lib/top_level.o /usr/local/gprolog-1.4.4/lib/debugger.o /usr/local/gprolog-1.4.4/lib/libbips_fd.a /usr/local/gprolog-1.4.4/lib/libengine_fd.a /usr/local/gprolog-1.4.4/lib/libbips_pl.a /usr/local/gprolog-1.4.4/lib/libengine_pl.a /usr/local/gprolog-1.4.4/lib/liblinedit.a -lm

環境に馴染むようにコンパイルすると、そのパソコンの石用にコンパイルしてくれてる。やっぱり 自前でコンパイルしておくのに限るな。

で、今更だけど、PLファイルがWAM用のコードになって、WAM用のアセンブラMAになって、 それが糞石用のコードに落ちて、最後はオブジェクトファイルになる。そしてそれを、 必要なライブラリィーとリンクして、実行コードにするとな。長い旅だのう。

そんじゃ、WAM用に変換されたやつ

predicate(child/2,2,static,private,monofile,global,[
    switch_on_term(6,1,fail,fail,fail),
      :
label(21),
    get_atom(tarao,0),
    get_atom(masuo,1),
    proceed]).

predicate(istrue/2,12,static,private,monofile,global,[
    pragma_arity(3),
    get_current_choice(x(2)),
    get_variable(x(3),1),
    put_value(x(2),1),
    put_value(x(3),2),
    execute('$istrue/2_$aux1'/3)]).

頑張って、読もうと思えば読めるな。次はMAコードな。

pl_code global X0_child__a2
        call_c     fast Pl_Switch_On_Term_Var_Atm(&Lpred1_6,&Lpred1_1)
        jump_ret

Lpred1_1:
        call_c     fast Pl_Switch_On_Atom(st(0),4)
        jump_ret
         :
pl_code global X0_sazae_family__a0
        call_c     fast Pl_Allocate(4)
        call_c     fast Pl_Put_Structure_Tagged(fn(0))
        move_ret   X(0)
        call_c     fast Pl_Unify_Atom_Tagged(ta(6))
        fail_ret
        call_c     fast Pl_Unify_Atom_Tagged(ta(1))
        fail_ret
        call_c     fast Pl_Put_Y_Variable(&Y(0))
        move_ret   X(1)

もう、何が何だか? そして、見ると目が腐ると言う糞石用のやつ。

X0_child__a2:
        movl    $Lpred1_6+0,%eax
        movl    $Lpred1_1+0,%edx
        call    Pl_Switch_On_Term_Var_Atm
        jmp     *%eax

Lpred1_1:
        movl    st+0,%eax
        movl    $4,%edx
        call    Pl_Switch_On_Atom
        jmp     *%eax
         :

もう、大変。