Prolog 入門

# はじめに
本記事はこれからPrologを始める人や、大学の定期試験の勉強をしている人などを対象にしています。筆者の備忘録的なものとして書きました。教科書もない、板書もとってないという人には役に立つかも...。
Prologのプログラミングについての本質的な説明ではありません。説明は適当なので簡単なプログラム集と思ってほしいです。
本記事を読めば、Prolog環境の導入からフィボナッチ数列のプログラムの作成やリストについての簡単なリストを使ったプログラムの作成までをできるようになるでしょう。（コピペで）

ここで扱う内容はPrologの基礎の基礎程度になると思います。筆者もここに書いてある以上のPrologの理解はしていません。

# 目次
1. Prologのインストール
2. Prologの実行
3. 人間関係の定義
4. 最大値と最小値
5. 自然数の定義
6. 奇数と偶数の判定
7. 足し算、掛け算の再定義
8. 階乗(N!)の計算
9. フィボナッチ数列
10. リスト
11. おわりに


# 1. Prologのインストール
本記事はUbuntuやWindowsのWSL(Windows Subsystem for Linux)でのインストールを想定しています。

下のコマンドを入力することでPrologがインストールされます。

```
$ sudo apt install swi-prolog
```

インストールされたら、コマンドに``prolog``と入力するとSWI-Prologが起動します。

```
$ prolog
Welcome to SWI-Prolog (threaded, 64 bits, version 7.6.4)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

?- 
```
これでUbuntu上でのPrologの開発環境が整いました。

# 2. Prologの実行
次に、実際にPrologの実行をします。まず、Prologファイルを作成します。拡張子は「.pl」です。下のfamily.plというファイルを作成します。

```prolog:family.pl
father_child(junicihro, shinjiro).   % 進次郎の父は純一郎
father_child(junicihro, kotaro).     % 孝太郎の父は純一郎

oyako(X, Y) :- father_child(X, Y).  % 父と子の関係なら親子
```

ファイルを作成したら、先程インストールした環境に```consult(family).```と入力しましょう。こうすることでfamily.plの内容がロードされます。「.pl」のファイル拡張子はいりません。

```
?- consult(family).
true.
```

``true.``とだけ出ていたら大丈夫です。``ERROR:~``が出ていたら大抵どこかプログラムを書き間違えています。終端のピリオド抜けや、C言語などの癖からセミコロンにしてしまっているミスがよくあります。

ここからいよいよPrologで自分が作ったプログラムに「質問」します。Prologは自分が作成したプログラム(=「事実」「規則、ルール」)に基づいて答えを返します。上のfamily.plでは「純一郎と進次郎は父子」「純一郎と孝太郎は父子」で「父子」なら「親子」であるとしました。

それでは、親子のペアをPrologに聞いてみましょう。

```
?- oyako(X, Y).
X = junichiro,
Y = shinjiro 
```

X=純一郎、Y=進次郎とだけ出てきました。孝太郎はどこへ行ったのでしょうか？？
上の状態になったら、セミコロン「；」を入力しましょう。↓

```
?- oyako(X, Y).
X = junichiro,
Y = shinjiro ;
X = junichiro,
Y = kotaro.
```

これですべてのパターンの親子の関係が出てきました。
では、次は純一郎の子供が誰かを質問しましょう。ここで気をつけるのは**変数の頭文字は大文字**にするということです。子供の部分を変数Sonとして質問します。

```
?- oyako(junichro, Son).
Son = shinjiro ;
Son = kotaro.
```

親が誰かを聞くときも上と同様です。進次郎の親を聞いてみると、

```
?- oyako(Parent, shinjiro).
Parent = junichro.
```

となります。Prologのプログラムの実行の流れは以上のような感じです。

# 3. 人間関係の定義
先程のfamily.plで書いた人間関係を拡張していきます。
純一郎の「妻」を定義します。純一郎の妻ということは、進次郎と孝太郎の母親を意味します。
また、進次郎と孝太郎は兄弟ですので、兄弟という関係を定義しようとします。
兄弟である条件は、「親が同じであること」です。
仮に、純一郎の妻を佳代子とします。これらの関係を表すと下のようになります。

```prolog:family2.pl
/* 事実 */
father_child(junichiro, shinjiro).  % 進次郎の父は純一郎
father_child(junichiro, kotaro).    % 孝太郎の父は純一郎

mother_child(kayoko, shinjiro).     % 進次郎の母は佳代子
mother_child(kayoko, kotaro).       % 孝太郎の母は佳代子

/* 規則  関係の記述*/ 

oyako(X, Y) :- father_child(X, Y).  % XとYが父子なら→XとYは親子関係である。
oyako(X, Y) :- mother_child(X, Y).  % XとYが母子なら→XとYは親子関係である。

sibling(X, Y) :- oyako(A, X),　     
     oyako(A, Y).                   % XとYは共通のAという親を持つならば→XとYは兄弟である。
```

「:-」の演算子は (左項) :- (右項)のとき、右の条件が成り立つならば左が成り立つ。と思っておけば良いと思います。
「:-」と「is」と「=」はProlog初心者には紛らわしいので混同しないように注意しましょう。

# 4. 最大値と最小値
第１引数と第２引数の最大値、最小値を第３引数(?)とするような関数を作成します。

```prolog:minmax.pl
minimum(X, Y, X) :- X =< Y. % <=ではなく=<であることに注意！
minimum(X, Y, Y) :- X > Y.

maximum(X, Y, X) :- X >= Y.
maximum(X, Y, Y) :- X < Y.
```

##発展(カット)

カットと言われる「!」を入れると例えば、`minimum(1, 3, Z).`と質問したとき`Z=1`を返されるという結果自体は全く変わらないが、`minimum(X, Y, Y) :- X > Y.`のときの規則を試さないで済むので計算機的にはカットを入れるといいようですね。**結果には全く変わりありません。**

```prolog:minmax2.pl
minimum(X, Y, X) :- X =< Y, !. 
minimum(X, Y, Y) :- X > Y.

maximum(X, Y, X) :- X >= Y, !.
maximum(X, Y, Y) :- X < Y.
```


# 5.自然数の定義
Prologではループというものは存在しません。すべてを再帰で記述します。
Prologでは再帰を用いて自然数も定義することができるのです。

```prolog:naturalnumber.pl
natural(0).
natural(s(X)) :- natural(X).
```
このPrologプログラムの世界では自然数を0,1,2,3,...ではなく0,s(0),s(s(0)),s(s(s(0))),...と表しています。
下のように0やs(0)は自然数かと質問すると、当然`true`と返してきます。

```
?- natural(0).
true.

?- natural(s(0)).
true.
```
自然数である`X`について質問すると下のようなことが無限に続きます。自然数は0,1,2,3,4,5,...と無限に続くからです。

```
?- natural(X).
X = 0 ;
X = s(0) ;
X = s(s(0)) ;
X = s(s(s(0))) ;
X = s(s(s(s(0)))) ;
X = s(s(s(s(s(0))))) 
```

# 6. 偶数と奇数の判定
偶数と奇数の判定をするプログラムを書いていきましょう。
## 偶数の判定
まずは偶数の判定から。
0は偶数なので事実として書き、それ以外の数字は再帰的に-2を繰り返します。Xが偶数であるなら必ず0で止まるので`true`を返します。奇数の場合は0で止まることはないので`false`を返します。

```prolog:even1.pl
even(0).            % 0は偶数という事実
even(X) :- X > 0,
    is(X1, X-2),    % X1 is X-2, と書いても良い
    even(X1).
```
下のように、偶数の判定ができています。

```
?- even(0).
true .

?- even(1).
false.

?- even(2).
true .

?- even(3).
false.
```
### s()を使った偶数判定
前述「5.自然数の定義」で行ったように**s()を使って偶数の判定をする**プログラムを書くと、

```prolog:even2.pl
even(0).
even(s(s(X))) :- even(X).
```
このようになります。テストに出そうですね:sweat_smile:
多分s()を使うパターンと使わないパターンで書け。とかってテストに出そうな気がします。
結果は下のようになります。

```
?- even(0).
true.

?- even(s(0)).
false.

?- even(s(s(0))).
true.

?- even(s(s(s(0)))).
false.
```
## 奇数の判定
奇数の判定も偶数とほぼ同じです。

```prolog:odd1.pl
odd(1).                 % 1は奇数という事実
odd(X) :- X > 1,
    is(X1, X-2),        % X1 is X-2, でも可
    odd(X1).
```
### s()を使った奇数判定
コードのみ

```prolog:odd2.pl
odd(s(0)).
odd(s(s(X))) :- odd(X).
```
# 7. 足し算、掛け算の再定義
s()と再帰を使って足し算と掛け算を定義してみましょう。下にコードを載せておきます。

```prolog:plus_times.pl
/* 足し算の定義 */
my_plus(X, 0, X).                               % 再帰の終了条件：第二引数が0となったとき
my_plus(X, s(Y), s(Z)) :- my_plus(X, Y, Z).     % Yを-1して、Zも-1する、これをYが0になるまで繰り返す

/* 掛け算の定義 */
my_times(_, 0, 0).                              % 再帰の終了条件：第二引数が0となったとき
my_times(X, s(Y), Z) :- my_times(X, Y, Z1),
    my_plus(X, Z1, Z).                          
```
イメージでは足し算はまずXが1を表すs(0)であるとします。その`X=s(0)`にY個分のs()を被せていく感じです。
# 8. 階乗(N!)の計算


```prolog:factorial.pl
factorial(0, 1).
factorial(N, F) :- N > 0,
    is(N1, N-1),
    factorial(N1, F1),
    is(F, N*F1).
```

```prolog:factorial2.pl
/* 足し算の定義 */
my_plus(X, 0, X).                               % 再帰の終了条件：第二引数が0となったとき
my_plus(X, s(Y), s(Z)) :- my_plus(X, Y, Z).     % Yを-1して、Zも-1する、これをYが0になるまで繰り返す

/* 掛け算の定義 */
my_times(_, 0, 0).                              % 再帰の終了条件：第二引数が0となったとき
my_times(X, s(Y), Z) :- my_times(X, Y, Z1),
    my_plus(X, Z1, Z). 

/* 階乗の計算 */
factorial(0, s(0)).
factorial(s(N), F) :- factorial(N, F1),
    my_times(s(N),F1, F).
```
# 9. フィボナッチ数列

```prolog:fibonacci.pl
fib(0, 0).
fib(1, 1).
fib(N, F) :- N > 1,
    is(N1, N-1),
    is(N2, N-2),
    fib(N1, F1),
    fib(N2, F2),
    is(F, F1+F2).
```

```prolog:fibonacci2.pl
my_plus(X, 0, X).
my_plus(X, s(Y), s(Z)) :- my_plus(X, Y, Z).

fib(0, 0).
fib(s(0), s(0)).
fib(s(s(N)), F) :- fib(s(N), F1),
    fib(N, F2),
    my_plus(F1, F2, F).
```
# 10. リスト

## リストの作成

```prolog:make_list.pl
make_list(0, []).
make_list(N, L) :- N > 0,
    is(N1, N-1),
    make_list(N1, L1),
    L = [N|L1].
```

## リストの中身が偶数であるか

```prolog:list_even.pl
% 引数Xが偶数の判定法1
even1(0).
even1(X) :- X > 0,
    is(X1, X-2),
    even1(X1).

% 引数Xが偶数の判定法2
even2(0).
even2(s(s(X))) :- even2(X).

% 引数[X|Xs]リストの要素すべてが偶数かの判定 
leven1([]).
leven1([X|Xs]) :- even1(X), %もしくはeven2(X),
    leven1(Xs).

```

# おわりに

最後まで、見ていただきありがとうございます。間違っているところは多々あるかもしれません。何かあればコメントなどで教えていただけると幸いです。[次の記事](https://qiita.com/a163236/items/5e0d0e373e87ca80b81e)はHaskellについてです。