Help us understand the problem. What is going on with this article?

PrologによるJSONパーサ

More than 1 year has passed since last update.

あしたのために その1

情報学科に通う学生さんの大半はPrologの授業があるのだそうです。試験の近くになるとTwitterには「Prolog、わかんねー」「Prolog、難しい」という呟きが急増します。でも、その体験は将来、大きな財産になる可能性があります。なぜなら

     並列の時代

だからです。電子回路の物理的な上限に達したCPUは並列化、マルチコアの方向にあります。これを活かすためには今まで主流であったオブジェクト指向では足かせになる場合があり、関数型プログラミングに移行しようというのがトレンドです。おっと、関数型と言えば、もうひとつ忘れちゃいませんか?

     論理型プログラミング

そうです。Prologは論理型プログラミング言語です。これも並列、マルチコアとの相性がいいのです。学校での勉強が将来、貴重な財産になるかもしれませんよ。

あしたのために その2

AI、人工知能がいよいよ商売になり始めたようです。Deep Leraning(以下「DL」)がどんどん進化しています。DLはお金、商売になりそうです。またまた、忘れてもらっちゃ困りますよ。Prologもかつては人工知能研究で重宝されていたのです。人間でいうと直感にあたるのがDLです。人間は大脳皮質が発達、論理的に考えることができるようになって大きく進化しました。DLには補佐役としてPrologが必要だと思うのですね。

だいぶ、前置きが長くなりました。現在、AI DLの主流はPythonです。PrologがPythonとデータ交換しようとすると・・・JSON ですよね。

動機

少し、おふざけが過ぎました。ここからは真面目に書きます。DLとPrologを連携させる必要がありました。このためのデータのやり取りをするのにどのような方法が良いか?と考えていました。AZ-Prologの開発者の方と話す機会があったとき、JSONは良いよ、という話があったのを思い出し、開発中のO-PrologにJSONパーサを搭載することとなりました。

規格

念のためISO-PrologにJSONのためのProlog規格がないか確認しました。ISO-Prologのワーキンググループは今でも存在しています。Prolog規格のコア部分の他にもPrologueという周辺規格を整備、そこにLength/2などが規定されています。ひょっとしてJSON関連も規定があるのでは?と思いました。comp.lang.prolog で尋ねてみました。規格はないそうです。それではと、デファクトスタンダートのSWI-Prologを参考にすることとしました。

動作例

O-Prologのライブラリとして収録しています。

O-Prolog Ver 1.59(Chika)
| ?- use_module(library(json)).
yes
| ?-json_read(X).
{ "name":"Demo term",

  "created": {
    "day":null,
    "month":"December",
    "year":2007
  },
  "confirmed":true,
  "members":[1,2,3]
}.
X = json([name='Demo term',created=json([day=null,month='December',year=2007]),confirmed=true,members=[1,2,3]])
yes

| ?- json_write(json([name='Demo term',created=json([day=null,month='December',year=2007]),confirmed=true,
members=[1,2,3]])).
{"name":"Demo term","created":{"day":null,"month":"December","year":2007},("confirmed":true,"members":[1,2,3])}yes
|

json_read/1はJSONのデータをjson()という述語に変換します。{ }はリストへ、文字列はアトムへ、数は数へ、そして : は = へと変換しています。

json_write/1はその逆変換です。Prologの述語になっているデータを変換して、元のJSONデータに変換しています。

実装方法

カール

カール { }はもともとISO-Prolog規格で要請されているものです。関数子が{}でそれに連言で使うコンマ(,)を演算子とみて引数として持つものです。

{one} = {}(one)

{one,two,tree} = {}(','(one,','(two,tree)))

このカールについてもユニフィケーションができるとJSONデータの扱いは楽です。O-Prologで動作するものだろうか?とやってみるとうまくいきました。

| foo({A:B}) :- write(A),write(B).
| ?- foo({1:2}).
12yes
|


でも、待てよ。これってO-Prologでたまたま動作しただけかも? デファクトスタンダートのSWI-Prologで動作するものなのだろうか?SWIでも同様でした。OKです。

文字列

{ }はPrologのデータなので、JSONの{ }をそのままread/1で読み込むことができます。これをunifyにより変換していけばいいので簡単です。1つだけ問題がありました。文字列です。ISO-Prolog規格には文字列という型はなく、リストで表すことになっています。

| ?- set_prolog_flag(double_quotes,codes).
yes
| ?- X = "abc".
X = [97,98,99]
yes
| ?- set_prolog_flag(double_quotes,chars).
yes
| ?- X = "abc".
X = [a,b,c]
yes

文字列については処理系ごとにデフォルトが異なるようです。O-Prologはcharsにしていました。というのはISO-Prologの議長のウルリッヒさんにMLで聞いたら、デフォルトはcharsだということだったからです。いずれの方法にしても"abc"はリストに変換されてしまいます。JSONではダブルクオートが使われます。Prologに変換するときにはアトムに変換しないといけません。atom_codes/2 atom_chars/2 を使えばリストになってしまったものをアトムに変換できるのですが、この際O-Prologにも文字列型を拡張として加えることにしました。

| ?- set_prolog_flag(double_quotes,string).
yes
| ?- X = "abc".
X = "abc"
yes

楽々ユニフィケーション

{ }カールと"abc"文字列、そしてPrologの強力なユニフィケーションがあれば、JSONのパース、変換は楽々です。まずはJSONからPrologへの変換です。カールの中にはいっているデータは次のパターンです。

〇〇 : △△ 

: はPrologでは中置記法演算子です。このまま読み取ることができます。左側の〇〇は文字列です。上記の拡張によりそのまんま"abc"のように取り込むことができるようにしました。それをアトムabcに変換すればOKです。

右側の△△にはいろいろと入ります。アトムの場合、リストの場合・・・その型ごとにPrologに変換するパターンを書いておきます。△△が{ }になっている場合もありますが、これは再帰的に処理すればいいですね。

次にPrologのデータをJSONに変換する場合です。

□□ = ×× 

の形式になっています。=は中置記法の演算子です。このまま読み取ることができます。左側にabcとあったら"abc"のように変換します。文字列を拡張しておいたのが役に立ちます。アトムabcを文字列に変換すればOKです。同様に数は数へ、リストは{ }カールへ。先ほどの逆の変換をしていきます。リストだったら再帰的に分解、変換していけばOKですね。

エンコーダー、デコーダーはPrologで書いたら楽ちんでした。両者あわせても60行程度のコンパクトなコードに収まりました。この手のパース、変換はPrologの得意分野であることを再認識しました。Prologのユニフィケーションの勝利です。

まとめ

さあ、みなさんもあしたに向かって :-(メダカ記号)を

     打つべし

(影の声)
(ねえねえ、今の若い人は 「あしたのジョー」 矢吹丈なんて知らないと思うよ)

コード

JSONのデータ型の扱いがかなり手抜きです。追々、改良していきます。

:- module(json).
:- export([json_read/1,json_read/2,json_write/1,json_write/2]).
:- end_module(json).

:- body(json).
:- set_prolog_flag(double_quotes,string).

json_read(X) :-
    read(Y),
    json_decode(Y,X).
json_read(S,X) :-
    read(S,Y),
    json_decode(Y,X).

json_decode(null,null).
json_decode(true,true).
json_decode(A:B,A1=B1) :-
    json_decode(A,A1),
    json_decode(B,B1).
json_decode({A:B},json([A1=B1])) :-
    json_decode(A,A1),
    json_decode(B,B1).
json_decode({(A:B),C},json([A1=B1|R])) :-
    json_decode(A,A1),
    json_decode(B,B1),
    json_decode_obj(C,R).
json_decode(X,X) :-
    list(X).
json_decode(X,X) :-
    number(X).
json_decode(X,Z) :-
    string(X),
    string_codes(X,Y),
    atom_codes(Z,Y).

json_decode_obj(A:B,[A1=B1]) :-
    json_decode(A,A1),
    json_decode(B,B1).
json_decode_obj((A,B),[A1|B1]) :-
    json_decode(A,A1),
    json_decode_obj(B,B1).


%----------------
json_write(X) :-
    json_encode(X,Y),
    write(Y).
json_write(S,X) :-
    json_encode(X,Y),
    write(S,Y).

json_encode(null,null).
json_encode(true,true).
json_encode(X,X) :-
    number(X).
json_encode(X,X) :-
    list(X).
json_encode(X,Y) :-
    atom(X),
    atom_chars(X,X1),
    string_chars(Y,X1).
json_encode(json(X),Y) :-
    functor(Y,'{}',1),
    json_encode_obj(X,Z),
    Y='{}'(Z).

json_encode_obj([A=B],A1:B1) :-
    json_encode(A,A1),
    json_encode(B,B1).
json_encode_obj([A=B|C],(A1:B1,C1)) :-
    json_encode(A,A1),
    json_encode(B,B1),
    json_encode_obj(C,C1).


:- end_body(json).


Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away