🤠

WebAssemblyでProlog

2022/12/22に公開

メリクリ!これは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版を試すにはこちらのデモサイトが便利です。

リポジトリ

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