Prolog で SPA を書く

# "Prolog" を知っていますか?

"Prolog" (プロログ) というのは、古くからあるプログラミング言語のひとつで、1972 年に開発されました。

Prolog はオブジェクト指向や関数型とも違う、**"論理プログラミング言語"**という枠に分類されています。

面白いことに、Prolog プログラミングにおいては、手続き型言語のように、コンピュータに動作の手順を教えるのとは違うやり方をします。
Prolog コードとして、"**事実 (fact)**"と"**規則 (rule)**"を記述するのです (下記例)。

```prolog
% [事実 (fact)] ソクラテスは人である。
human(socrates).
% [規則 (rule)] 人は死ぬ。
mortal(X) :- human(X).
```

こうして記述された Prolog プログラムコードを、Prolog の処理系が読み込んだのち、ユーザーは処理系に対して "問い合わせ (query)" をすることができるようになります (下記例)。

```prolog
% [問い合わせ (query)] 死ぬ X とは何か?
?- mortal(X).
```

すると、Prolog の処理系は解答 (Solution) を探索し、もし見つかればそれを応答として返す、というプログラミング言語です。

```prolog
% [解答 (Solution)] 死ぬ X とはソクラテスである
X = socrates
```

このように Prolog はなかなかに奇妙で面白いプログラミング言語だと思います。

自分は、Prolog を知るまでは、手続きではなく論理を記述することで PC アプリケーションを作ることができるとは思い至りませんでした。

# Prolog で SPA を書ける?

さてところで、自分は日頃、"Blazor" (ブレイザー) という、JavaScript ではなくて C# で Single Page Client Web Application (いわゆる SPA) を実装することのできる処理系でプログラミングをしています。

- _[Blazor | Build client web apps with C# | .NET](https://blazor.net)_

Blazor の仕組みは、Web ブラウザ上の WebAssembly エンジンを使って、.NET の中間言語 (MSIL) を実行することで実現されています。

そしてまた、自分は、Prolog 処理系を C# で実装した "C#Prolog" というライブラリを fork していました。

- _[C#Prolog - A C# implementation of Prolog](https://github.com/jsakamoto/CSharpProlog)_

そんなある日、
**「Blazor と C#Prolog を組み合わせたら、Prolog で SPA を書くことができるんじゃね?」**
というアイディアがふと思いつきました。

そこでこの年末年始 (2020-2021) の休暇を利用して、このアイディアが実現できるか試してみることにしました。
そうしてついに、実際に Prolog で SPA を書くことを実現したのです。

# **"Spprologa"** - Prolog で SPA を作るライブラリ

ということで、**"Spprologa"** と名付けた .NET 用のライブラリを公開開始しました。

- _[Spprologa - "Build client web apps with Prolog."](https://github.com/jsakamoto/Spprologa)_

この "Spprologa" ライブラリを使うことで、SPA を Prolog で実装することができるようになります。

ちなみにライブラリ名の "Spprologa" は、単純に "**S**ingle **P**age **Prolog** **A**pplication" の頭文字を取っただけですw
(発音は... スプロローガ、とかでしょうか)

ここまでの説明で既にお察しの方もいるかと思いますが、"Spprologa" は Blazor と C#Prolog の橋渡しとしてのライブラリであり、できあがるのは詰まるところ Blazor WebAssembly アプリなので、**すべての Prolog コードは (サーバー側ではなく) ブラウザ上で実行されます。**

Prolog コードはブラウザの WebAssembly 上で走る "C#Prolog" によって処理される仕組みであり、Prolog コードを JavaScript にトランスパイルしているわけではないです。

つまり "Spprologa" アプリケーションは、GitHub Pages, Firebase, Netlify, Azure Static Web などなど、最低限、静的コンテンツを返せる Web サーバーであれば、どこへでも配置することができるということです。

実際、下記 URL のとおり GitHub Pages 上にライブデモサイトを配置してあります。

- ["Spprologa" ライブデモサイト](https://jsakamoto.github.io/Spprologa/)

# "Spprologa" プログラミングしてみる

ということで、実際の "Spprologa" アプリケーションプログラミングがどのようなものになるのか、少し紹介したいと思います。

なお、"Spprologa" は Blazor に基づいていることから、Blazor プログラミングができることが前提となっています。
そのため、以降では Blazor プログラミングについてはとりあえずわかっている前提で、多くを省略して説明しています。
悪しからずご了承ください。

![fig.1](https://raw.githubusercontent.com/jsakamoto/Spprologa/main/.assets/demo.gif)

## 準備

"Spprologa" は Blazor に基づいているので、.NET の SDK (ver.5.0.101 かそれ以降) がインストールされている必要があります。
.NET SDK を各自お使いの PC または Mac にインストールするには、下記公式サイトを見てみてください。

- _["Download .NET"](https://dotnet.microsoft.com/download)_

.NET SDK のインストールが済んだら、続けて、下記コマンドの実行にて、"Spprologa" アプリケーションのプロジェクトテンプレートを事前にインストールしておきます。

```shell
$ dotnet new -i Spprologa.Templates::0.0.1-preview.4.0.0.0
```

## 新規 "Spprologa" プロジェクトを作って実行する

ひとたびプロジェクトテンプレートのインストールまで済んだら以降、下記のように `dotnet new` コマンドを実行することで、カレントフォルダに新規 Spprologa アプリケーションプロジェクトを生成することができます。

```shell
$ dotnet new spprologa
```

新しい "Spprologa" アプリケーションプロジェクトが生成されたら、続けて以下のコマンドを実行することで、アプリケーションがビルド・実行開始され、さらにアプリケーションの URL が既定のブラウザで自動で開かれます。

```shell
$ dotnet watch run
```

あとは、この "Spprologa" アプリケーションプロジェクト上で、コーディングしてけば OK です。

## 発行するには

作成した "Spprologa" アプリケーションをどこかの Web サーバーに配置するには、`dotnet publish` コマンドを使って、「発行」という作業をする必要があります。

具体的には下記のようにコマンド実行します。

```shell
$ dotnet publish -c:Release -o:path/to/output
```

発行が完了すると、その "Spprologa" アプリの実行に必要なコンテンツファイル群が `path/to/output/wwwroot` フォルダに吐き出されていますので、同フォルダの内容を Web サーバーに配置すればよいです。

## 機能の概念

"Spprologa" アプリケーションのプログラミングは、以下の 5 つの機能に基づいています。

### 1. プロジェクト内の `*.razor.prolog` ファイルは自動的に読み込まれ (consult され) ます

```prolog:/Pages/Foo.razor.prolog
% この Prolog コードは、"Foo.razor" コンポーネントが初めてレンダリングされる1回だけ、
% Prolog エンジンに読み込まれます (consult されます)。

input(name, "").

bird("swallow", canfly).
bird("penguin", canswim).
canfly(Name) :- bird(Name, canfly).

classify(Name, "can fly") :- canfly(Name).
classify(Name, "is bird, but can't fly.") :- bird(Name, _).
classify(_, "isn't bird.").

check :-
	input(name, Name), classify(Name, Message),
	retractall(message(_, _)), asserta(message(Name, Message)).
```

### 2. 問い合わせ (query) の解答の変数値を、HTML にバインドします

**`@query("...")`** を使うことで、引数に指定した問い合わせ (query) の解答 (solution) から、その変数の値を取得して、HTML コンテンツとしてレンダリングできます。

```html:/Pages/Foo.razor
...
<span>
  <!-- 👇 ここは "swallow" と出力される -->
  @query("canfly(Name)")
</span>
```

### 3. ユーザー入力は "事実 (fact)" に双方向バインドします

**`@fact("...")`** を使って、引数に指定した「事実 (fact)」の定義に、`input` 要素へのユーザー入力を双方向バインドします。

```html:/Pages/Foo.razor
...
<input @bind='@fact("input(name, {0})").as_string' />
```

例えば上記 `input` 要素に "gopher" と入力された場合、いったんすべての `input(name, _)` という事実 (fact) はすべて撤回 (retract) され、代わりに `input(name, "gopher")` という事実 (fact) が新たに宣言 (assert) しなおされます。

### 4. イベントの発生を、問い合わせ (query) の呼び出しにバインドします

**`@then("...")`** を使って、イベントの発生時に、引数に指定した問い合わせ (query) が呼び出されるようにします。

```html:/Pages/Foo.razor
...
<button @onclick='@then("check")'>Check</button>
```

ユーザーが上記ボタンをクリックすると、`check` という問い合わせ (query) が呼び出されます。

### 5. 事実を列挙します

**`<Case>`** コンポーネントを使って、指定した問い合わせの全ての解答 (solution) を列挙し、各解答の変数値を HTML コンテンツとしてレンダリングします。

```html:/Pages/Foo.razor
...
<ul>
  <Case Query="bird(Name, Ability)">
    <li>@context["Name"] - @context["Ability"]</li>
  </Case>
<ul>
```

この例において、上記コードは以下の HTML を出力します。

```html
<ul>
  <li>swallow - canflay</li>
  <li>penguin - cannotflay</li>
</ul>
```

# どんな "Spprologa" アプリケーションが作れる?

先に紹介したライブデモサイトで見ることができるように、例えば、["地図の塗り分け"](https://jsakamoto.github.io/Spprologa/mapcoloring)を作って見ました。

![map coloring](https://dev-to-uploads.s3.amazonaws.com/i/kyr7nfiwd4zv9ujkiwwx.png)

Prolog コードの中核部分の抜粋は以下のとおりです。

```prolog
% define that what colors are there.
color(red).
color(green).
color(blue).
color(yellow).

% The implementation of the map coloring.
color_of_map(Alabama, Mississippi, Georgia, Tennessee, Florida) :-
    color(Alabama), color(Mississippi), color(Georgia), color(Tennessee), color(Florida),
    Tennessee \= Mississippi,
    Tennessee \= Alabama,
    Tennessee \= Georgia,
    Mississippi \= Alabama,
    Alabama \= Georgia,
    Alabama \= Florida,
    Georgia \= Florida.
```

その他には、["数独" (但し 9x9 ではなく 4x4 のマス) のソルバー](https://jsakamoto.github.io/Spprologa/4x4sudoku)も作ってみました。

![Sudoku (4x4)](https://dev-to-uploads.s3.amazonaws.com/i/lt1y1k6tezq5u1q3xjh7.png)

こちらのソルバー部分の Prolog コードは下記のとおりです。

```prolog
sudoku(Puzzle, Solution) :-
    Solution = Puzzle,
    Puzzle = [
        S11, S12, S13, S14,
        S21, S22, S23, S24,
        S31, S32, S33, S34,
        S41, S42, S43, S44
    ],
    NUMBERS = [1,2,3,4],

    % rows
    permutation(NUMBERS, [S11, S12, S13, S14]),
    permutation(NUMBERS, [S21, S22, S23, S24]),
    permutation(NUMBERS, [S31, S32, S33, S34]),
    permutation(NUMBERS, [S41, S42, S43, S44]),

    % columns
    permutation(NUMBERS, [S11, S21, S31, S41]),
    permutation(NUMBERS, [S12, S22, S32, S42]),
    permutation(NUMBERS, [S13, S23, S33, S43]),
    permutation(NUMBERS, [S14, S24, S34, S44]),

    % square
    permutation(NUMBERS, [S11, S12, S21, S22]),
    permutation(NUMBERS, [S31, S32, S41, S42]),
    permutation(NUMBERS, [S13, S14, S23, S24]),
    permutation(NUMBERS, [S33, S34, S43, S44]).
```

面白いことに、いずれの Prolog プログラムも、**解き方のアルゴリズムは実装/記述していません**。

代わりに、それらパズルの**制約と規則を Prolog コードとして書き表しただけ**なのです。

それでちゃんとソルバーとしてプログラムが成り立つという点が、個人的には、Prolog の面白さであるかな、と思っています。


# た・だ・し。あくまで「概念実証」プロジェクト

ここまでいろいろ書いてきましたが、しかし "Spprologa" ライブラリの開発は、「Prolog で SPA を実装できるようになるだろうか? なるとすればそれはどのような形になるだろうか?」という、あくまでも私個人の興味を満足されるために始まりました。

なので、**おそらくは私の興味が尽き次第、"Spprologa" ライブラリの開発は停滞し、放置されることになるだろうなぁ、と予想していますw**

しかしですね、"Spprologa" ライブラリは Mozilla Public License としてリリースしています。

つまり、**いつでも・誰でも・自由に**、"Spprologa" ライブラリを **fork** し、**開発を継続**しても構わないわけです。
ということで、もしそのような方がいらっしゃれば、もちろん私としては大歓迎です。👍

**以上、"Spprologa" ライブラリが、Prolog プログラミングを楽しむ一材にでもなれば幸いです!**