WebAssemblyでProlog
メリクリ!これはProlog Advent Calendarの1日目(😅)の記事です。最近Trealla PrologというPrologインタプリタをWASM化したので紹介しようと思います。
Prologについて
Prologとは論理プログラミング言語の1つです。論理プログラミングはかなり特殊で、「プログラミング言語なんて全部一緒でしょ」という人は論理プログラミング言語を絶対に使ったことないです(笑)。手続き型っぽいことは出来なくはないですが、主に関連性や論理的な条件を宣言して、クエリを投げる感じの使い方になります。少しSQLに似ているが、SQLと違って同図像性などを重視しています。
Trealla Prologについて
Trealla PrologはAndrew Davison氏によるMITライセンスのPrologインタプリタです。
特徴としては
- C製 (C99)
- ISO Prolog標準
- 外部依存が(ほとんど)ない
- コンパイル・移植しやすい ←俺得
-
WASM/WASI対応(私がコントリビュートしました!)
- 現在唯一のWASI対応のPrologです - コンパクト(gzipで250KB程度)
- 結構速い・文字列が最適化されている
- モジュールが使える
- DCGs, Variable Attributes, Goal/Term Expansionなど、便利な機能が様々
- Scryer Prologと互換性が良い
- CLP(Z)はまだ未対応
WASM版を試すにはこちらのデモサイトが便利です。
リポジトリ
- 本家: https://github.com/trealla-prolog/trealla
- WASM実験用: https://github.com/guregu/trealla
- 少しずつ本家にマージしていく予定です
- Javascriptのライブラリ: https://github.com/guregu/trealla-js
- Goのライブラリ: https://github.com/trealla-prolog/go
WASM版をビルドする
ビルドは簡単です。
まず、WASMをビルド出来る環境を用意します。
そして make wasm
を実行すると tpl.wasm
が出来上がります。
また、WASI向けのバイナリは wapm.io/guregu/treallaでゲット出来ます。
Wizerで起動時間を大幅にカット出来ました。
Javascriptと連携する
Treallaが簡単に使えるtreallaというNPMパッケージをメンテしています。WASMのバイナリが組み込まれています。Typescriptの型定義も入っています。
ブラウザで使う
<script type="module">
import { Prolog, load } from 'https://esm.sh/trealla';
// ランタイムを初期化
await load();
// Prologのインタプリタを作る
// 複数のインタプリタを同時に使っても大丈夫です。
// 同じインタプリタに複数のクエリを投げても大丈夫です。
// それぞれのインタプリタはデータベースやファイルシステムが違います。
const pl = new Prolog();
// クエリを投げてみる
const query = pl.query('between(1, 5, X).');
for await (const answer of query) {
console.log(answer);
}
</script>
このような結果が返ってきます
{
"result": "success",
"answer": {"X": 1}
}
{
"result": "success",
"answer": {"X": 2}
}
{
"result": "success",
"answer": {"X": 3}
}
...
Node.jsで使う
$ npm install trealla
後はブラウザと一緒です!今度はバーチャルファイルシステムを使ってみます。
import { Prolog, load } from 'trealla';
await load();
const pl = new Prolog();
// 「greeting.pl」を作成
pl.fs.open("/greeting.pl", { write: true, create: true }).writeString(`
:- module(greeting, [hello/1]).
hello(world).
hello(世界).
`);
// consultはPrologで「コードを読み込む」という意味です
await pl.consult("greeting");
// await pl.consult("/greeting.pl"); と同じです
const query = pl.query(`use_module(greeting), hello(Planet), format("Hello ~s!", [Planet]).`);
for await (const answer of query) {
console.log(answer);
}
このような結果が返ってきます
{
"result": "success",
"answer": {"Planet": {functor: "world"}},
"stdout": "Hello, world!"
}
{
"result": "success",
"answer": {"Planet": {functor: "世界"}},
"stdout": "Hello, 世界!"
}
PrologからJSを呼ぶ
参考: https://github.com/guregu/trealla-js#prolog-to-javascript
Javascriptをeval
greet :-
js_eval_json("return prompt('Name?');", Name),
format("Greetings, ~s.", [Name]).
非同期処理
Promiseを返すと自動的にyieldとawaitしてくれます。
?- js_eval_json("return fetch('http://example.com').then(x => x.text());", Src).
Src = "<html><head><title>Example page..."
js_fetch/3
なども入っています。
await pl.queryOnce(
`js_fetch("https://httpbin.org/post", Content, [as(json), body({"hello": "world"}), method(post), headers([foo-bar])]).`),
Content = {"args": {},"data":"{\\"hello\\":\\"world\\"}","files": {},"form": {},"headers":{"Accept":"*/*","Accept-Encoding":"br, gzip, deflate","Accept-Language":"*","Content-Length":"17","Content-Type":"text/plain;charset=UTF-8","Foo":"bar","Host":"httpbin.org","Sec-Fetch-Mode":"cors","User-Agent":"undici","X-Amzn-Trace-Id":"Root=1-63a4158a-1166042d40484a67414fc7f0"},"json":{...),"origin":"203.145.124.247","url":"https://httpbin.org/post"}.
さらに
JSのライブラリと同様なGo言語のライブラリも作りました。明日紹介します!
Prologを組み込みましょう!
Discussion