Prologの新しいシンタックスの提案 2

先日、新しい Prolog の構文を考えたわけですが、課題として `$` を使うよりも `_` を使ったほうがより分かりやすいのではないかという話をしました。

そこで今回は、課題となっていた `_` を使ったバージョンを開発してみました。 アンダーバーを手前に付けるか、後ろに付けるかは好みの問題でもありそうなので実際に使ってみてどちらがいいかを決めると良さそうなのでどちらでも良いことにしました。

ヘッド部のスキャンを最初と最後に2回行うことで、リターン値となる変数は特にアンダーバーを付けずに読み込めることにしてみました。

## 実装

```prolog
% npl2.pl
read_file_terms(F,R) :-
    open(F,read,FP),read_terms(FP,R,[]),close(FP).
read_terms(FP, Terms, Tail) :-
    read_term(FP, C1, [variable_names(Vs)]),maplist(call,Vs),
    read_terms(C1, FP, Terms, Tail).
read_terms(end_of_file, _, Tail, Tail) :- !.
read_terms(C, FP, [C|T], Tail) :-
    read_term(FP, C2, [variable_names(Vs)]),maplist(call,Vs),
    read_terms(C2, FP, T, Tail).

% 述語１つを変換

conv_terms(_,[],[],I,I) :- !.
conv_terms(E,[C|Cs],[C2|Cs2],I,I2) :- conv_goal(E,C,C2,I,I1),conv_terms(E,Cs,Cs2,I1,I2).

conv_goal(_,V,V,I,I) :- var(V),!.
conv_goal(_,V,V3,I,I) :- atom(V),(concat('_',V2,V);concat(V2,'_',V)),memberchk(V2=V3,I),!.
conv_goal(_,V,V3,I,[V2=V3|I]) :- atom(V),(concat('_',V2,V);concat(V2,'_',V)).
conv_goal(E,V,V2,I,I) :- atom(V),memberchk(V=V2,E),!.
conv_goal(_,V,V,I,I) :- atomic(V),!.
conv_goal(E,V,V2,I,I2) :- V=..[C|Cs], conv_terms(E,Cs,Cs2,I,I2), V2=..[C|Cs2].

simpl(I1,I2) :- simpl([],I1,I2).
simpl(E,[],E).
simpl(E,[X=_|I1],I2) :- memberchk(X=_,E),simpl(E,I1,I2).
simpl(E,[V|I1],I2) :- simpl([V|E],I1,I2).

merge(S1,[],S1).
merge(S1,[X=V|S2],S3) :- member(X=V,S1),merge(S1,S2,S3).
merge(S1,[X=V|S2],S3) :- merge([X=V|S1],S2,S3).

conv_goals((A,B),(A1,B1)) --> conv_goals(A,A1),conv_goals(B,B1).
conv_goals((A;B),(A1;B1),I,I3) :-
    conv_goal(I,A,A1,[],I1),conv_goal(I,B,B1,[],I2),
    simpl(I1,S1),simpl(I2,S2),merge(S1,S2,S3),append(S3,I,I3).
conv_goals(A,A1,I,I2) :- conv_goal(I,A,A1,[],I1),append(I1,I,I2).

% 文を変換
conv_stmt((:- V),(:- V2)) :- !,conv_goals(V,V2,[],_).
conv_stmt((H :- V),(H3 :- V2)) :- !,conv_goals((H,V),(H2,V2),[],I),!,
  conv_goals(H2,H3,I,_).
conv_stmt(V,V2) :- !,conv_goals(V,V2,[],_).
% 文のリストを変換
conv_stmts(Vs,Vs2) :- maplist(conv_stmt,Vs,Vs2).

assert1(:- V) :- call(V).
assert1(V) :- assertz(V).
consult1(F) :- read_file_terms(F,R),conv_stmts(R,R2),maplist(assert1,R2).
:- current_prolog_flag(argv, Argv),maplist(consult1,Argv).
```

## 実行例

アンダーバーを手前に付ける例です:

```prolog
% eval2.npl

eval(_Γ,_x,v) :- member(x=_v,Γ).
eval(_Γ,_i,_i) :- integer(i).
eval(_Γ,_a+_b,v) :- eval(Γ,a,_v),eval(Γ,b,_v2),_v is v+v2.
eval(_Γ,_a*_b,v) :- eval(Γ,a,_v),eval(Γ,b,_v2),_v is v*v2.
eval(_Γ,_x=_a;_b,v) :- eval(Γ,a,_v),eval([x=v|Γ],b,_v).

:- eval([],x=1*2+3*4;x,_r),writeln(r).
:- halt.
```

リターンの変数 `v` などにはアンダーバーがついていません。

こちらは、アンダーバーを後ろに付けた例です:

```prolog
% eval3.npl

eval(Γ_,x_,v) :- member(x=v_,Γ).
eval(Γ_,i_,i_) :- integer(i).
eval(Γ_,a_+b_,v) :- eval(Γ,a,v_),eval(Γ,b,v1_),v_ is v+v1.
eval(Γ_,a_*b_,v) :- eval(Γ,a,v_),eval(Γ,b,v1_),v_ is v*v1.
eval(Γ_,x_=a_;b_,v) :- eval(Γ,a,v_),eval([x=v|Γ],b,v_).

:- eval([],x=1*2+3*4;x,r_),writeln(r).
:- halt.
```

個人的には後ろにアンダーバーを付けるほうが、`x:int` などの型を書くと新たな変数という意味の型を省略して `x_` と書いている感じがして良い気がします。

```prolog
% eval5.npl

eval(_Γ,_x, v) :- member(x =_v, Γ).
eval(_Γ,_i,_i) :- integer(i).
eval(_Γ,_a +_b, v) :- eval(Γ, a,_v),eval(Γ, b,_v1),_v is v + v1.
eval(_Γ,_a *_b, v) :- eval(Γ, a,_v),eval(Γ, b,_v1),_v is v * v1.
eval(_Γ,_x =_a;_b, v) :- eval(Γ, a,_v),eval([x = v|Γ], b,_v).

:- eval([], x = 1 * 2 + 3 * 4; x,_r), writeln(r).
:- halt.
```

しかし、普通はスペースを左側に入れた方が綺麗なので、そのスペースに `_` を書くイメージで考えるとやっぱり手前が良いかもしれません。
うーむ。どちらが良いのだろう。。。

```prolog
% eval6.npl

eval(_Γ,_x, v) :- member(x = v_, Γ).
eval(_Γ,_i,_i) :- integer(i).
eval(_Γ,a_+_b, v) :- eval(Γ, a,_v),eval(Γ, b,_v1),v_ is v + v1.
eval(_Γ,a_*_b, v) :- eval(Γ, a,_v),eval(Γ, b,_v1),v_ is v * v1.
eval(_Γ,x_=_a;_b, v) :- eval(Γ, a,_v),eval([x = v|Γ], b,_v).

t(_Γ,_x,t) :- atom(x),member(x:t_, Γ).
t(_Γ,_i,Int) :- integer(i).
t(_Γ,a_+_b,Int) :- t(Γ,a,Int),t(Γ,b,Int).
t(_Γ,a_*_b,Int) :- t(Γ,a,Int),t(Γ,b,Int).
t(_Γ,x_=_a;_b,t) :- t(Γ,a,_t),t([x:t|Γ],b,_t).

run(_e, r, t) :- t([], e,_t), eval([], e,_r).
:- run(x = 1 * 2 + 3 * 4; x,_r,_t), writeln(r:t).
:- a_ = 1, a_ is a+1, a_ is a+1, writeln(a).
:- a_:Int = x:_t, writeln(a:t).
:- halt.
```

データの流れの方向によって残像のようなイメージで `_` を好きにかき分けて良いと考えたら良いかもしれません。
`a_ = 1` は右から左へシュッと現れる変数 `a` そして `a_ is a + 1` でその `a` は右から左へと書き換わるｗ
わかりますかね？基本は右から左へ流れるのです。しかしたまに戻ったり、`a_+_b` はブワッと展開されたりする。
どちらも使えると楽しいかもしれませんｗ

## まとめ

新たにアンダーバーを使えるように変更したバージョンを作ってみました。
結果としてより使用しやすくなったと思います。

実験としてはこれで十分だと思いますが、アンダーバーのみの場合はプレースホルダとするような処理はないなど、実用上は色々問題があるのでそれらの問題を解決できると本格的に使用できるようになるのではないかと思います。

`'_a'` のように書いても内部的に atom と認識されたあと、変数になってしまうのも困るのでパーサ本体に手をいれたくなるところでもあります。