プログラマー向けの（？）数理論理学（１）数学的言明

プログラマー向けというか、数理論理学の枠組みをプログラムとして実装したい人とか形式化数学をやりたい人向けの記事かもしれません（形式化数学については[こちらの記事](https://qiita.com/pizyumi/items/24e5ba843133059fd74a)もどうぞ）。

また、1年前にも似たような内容の連載を書こうとして1記事目で止まってしまったので今回最初から書き直すことにしました。

### 数理論理学とは

数理論理学とは数学的な議論を厳密に行うために必須となる数学のツールであり、たとえば、「`2`は素数である」というような数学的言明やより複雑なものを数学的に取り扱うための理論です。

### 数学的言明

数学における全ての主張は数学的言明として表されますが、数学的言明は真である（正しい）か偽である（正しくない）かが明確に判断できるような形になっていなければなりません。つまり、曖昧な部分がなく完全に明確に述べられていなければなりません。

そして、複雑な数学的言明はより単純な数学的言明が論理的に組み合わされた形となることもあります。

単純な数学的言明から複雑な数学的言明を組み立てる際に使用されるのが下のような**論理結合子**と呼ばれるものです。

* **否定**・・・「～でない」という意味です。たとえば、「`2`は素数でない」という言明は「`2`は素数である」という言明を否定したものです。記号は`￢`を使います。
* **連言**・・・「～かつ・・・」という意味です。たとえば、「`2`は素数であり、かつ、`3`も素数である」という言明は「`2`は素数である」という言明と「`3`は素数である」という言明の連言です。記号は`∧`を使います。
* **選言**・・・「～または・・・」という意味です。たとえば、「`2`は素数であるか、または、`3`は素数である」という言明は「`2`は素数である」という言明と「`3`は素数である」という言明の選言です。記号は`∨`を使います。
* **含意**・・・「～ならば・・・」という意味です。たとえば、「`2`が素数であるならば`3`は素数である」という言明は「`2`は素数である」という言明と「`3`は素数である」という言明の含意です。記号は`→`を使います。
* **同値**・・・「～ならば・・・かつ・・・ならば～」という意味です。たとえば、「`2`が素数であるならば`3`は素数であり、かつ、`3`が素数であるならば`2`は素数である」という言明は「`2`は素数である」という言明と「`3`は素数である」という言明の同値です。記号は`↔`を使います。

このような論理結合子によって組み立てられた複雑な数学的言明は**部品となった数学的言明が真であるか偽であるかによってその真偽も決まる**ことに注意してください。

たとえば、「`2`は素数でない」という言明の真偽は「`2`は素数である」という言明が真である場合には必ず偽となるし、逆に偽である場合には必ず真となります。

また、具体的な数学的言明は無数にありますが（たとえば、「`2`は素数である」、「`3`は素数である」、「`4`は素数である」・・・）、論理結合子それぞれの作用は変わりません（たとえば、「`2`は素数でない」という言明と「`3`は素数でない」という言明における「～でない」という否定の論理結合子の働きは同じ）。

ですから、論理結合子の作用を考える際には具体的な数学的言明それぞれにおける作用を1つずつ考えるのではなく、あらゆる数学的言明を表すメタ変数`P`、`Q`、`R`などを導入し、「`P`でない」、「`P`かつ`Q`」などの真偽が`P`、`Q`などの真偽に応じてどのようになるかを考えます。

それを表形式で表すと下のようになります。ただし、`T`は「真」を表し、`F`は「偽」を表します。

* `P`でない（`￢P`）

|`P`|`￢P`|
|---|---|
|`T`|`F`|
|`F`|`T`|

* `P`かつ`Q`（`P ∧　Q`）

|`P`|`Q`|`P ∧ Q`|
|---|---|---|
|`T`|`T`|`T`|
|`T`|`F`|`F`|
|`F`|`T`|`F`|
|`F`|`F`|`F`|

* `P`または`Q`（`P ∨　Q`）

|`P`|`Q`|`P ∨ Q`|
|---|---|---|
|`T`|`T`|`T`|
|`T`|`F`|`T`|
|`F`|`T`|`T`|
|`F`|`F`|`F`|

* `P`ならば`Q`（`P →　Q`）

|`P`|`Q`|`P → Q`|
|---|---|---|
|`T`|`T`|`T`|
|`T`|`F`|`F`|
|`F`|`T`|`T`|
|`F`|`F`|`T`|

* `P`ならば`Q`かつ`Q`ならば`P`（`P ↔　Q`）

|`P`|`Q`|`P ↔ Q`|
|---|---|---|
|`T`|`T`|`T`|
|`T`|`F`|`F`|
|`F`|`T`|`F`|
|`F`|`F`|`T`|

### 数学的言明の取り扱い（プログラム上）

ここから下は数学的言明をプログラムでどう実装するかという話なので、プログラミングに興味のない方は飛ばして次の記事に進んでください。

---

それではこのような数学的言明をプログラム上で扱うにはどのようにすれば良いでしょうか？

まずプログラム上で、たとえば、「`2`は素数である」というような具体的な数学的言明を扱うには「`2`」のような数学的対象物や「素数」のような数学的概念をプログラムで定義できるような機構を導入しなければならなくなり、それを今説明するのは難しいので、**今回は`P`、`Q`、`R`のようなあらゆる数学的言明を表すメタ変数を数学的言明として取り扱うことを考えます**。

**最終的にはこのようなメタ変数に具体的な数学的言明を代入することが可能になる**ことになります。

ということは最初に`P`、`Q`、`R`のようなメタ変数をプログラム上で作れるようにしなければなりません。

このメタ変数は

* 無限に作れなければならない。
* 重複してはならない。

ので、`P`、`Q`、`R`のような変数名を採用するよりは<code>P<sub>1</sub></code>、<code>P<sub>2</sub></code>、<code>P<sub>3</sub></code>のような`P`の後に連番を付加するスタイルの方が実装しやすいですので、この方法で実装しましょう。

メタ変数を作る関数は下のようになります。

```js
function create_proposition (index) {
  return {
    type: 'prop',
    subtype: 'atom',
    index: index
  }
}
```

単純な関数です。

メタ変数の`P`の後の数字を`index`引数として受け取り、メタ変数を表すオブジェクトを返すだけの関数です。

このオブジェクトの`type`プロパティは`prop`としています。`type`プロパティはオブジェクトの種類を表すためのもので、`prop`はこのオブジェクトが数学的言明を表すことを示しています。

`prop`はpropositionの略なのですが、たとえば、Coqでも`Prop`型として採用されています。

そして、`subtype`プロパティは`atom`としています。`subtype`プロパティはオブジェクトの更に細かい種類を識別するためのもので`atom`はこのオブジェクトがメタ変数を表すことを示しています。

`atom`は「原子的な」という意味で、数学的言明の最小単位であることを表しています。

そして、`index`プロパティには`index`引数の値を格納します。

メタ変数を作る関数が作成できたので、次は論理結合子によって組み立てられる数学的言明を作る関数を実装していきます。

基本的な実装方法はメタ変数の場合と同じで、作成する数学的言明の材料となる数学的言明を表すオブジェクトを引数として受け取り、新しい数学的言明を表すオブジェクトを作成し、それを返すだけです。

ですから下のようになります。

```js
function create_negation (p) {
    if (p.type !== 'prop') {
        throw new Error('not prop.');
    }

    return {
        type: 'prop', 
        subtype: 'neg', 
        sub: p
    };
}

function create_conjunction (p1, p2) {
    if (p1.type !== 'prop') {
        throw new Error('not prop.');
    }
    if (p2.type !== 'prop') {
        throw new Error('not prop.');
    }

    return {
        type: 'prop', 
        subtype: 'conj', 
        sub1: p1, 
        sub2: p2
    };
}

function create_disjunction (p1, p2) {
    if (p1.type !== 'prop') {
        throw new Error('not prop.');
    }
    if (p2.type !== 'prop') {
        throw new Error('not prop.');
    }

    return {
        type: 'prop', 
        subtype: 'disj', 
        sub1: p1, 
        sub2: p2
    };
}

function create_implication (p1, p2) {
    if (p1.type !== 'prop') {
        throw new Error('not prop.');
    }
    if (p2.type !== 'prop') {
        throw new Error('not prop.');
    }

    return {
        type: 'prop', 
        subtype: 'imp', 
        sub1: p1, 
        sub2: p2
    };
}

function create_equivalence (p1, p2) {
    if (p1.type !== 'prop') {
        throw new Error('not prop.');
    }
    if (p2.type !== 'prop') {
        throw new Error('not prop.');
    }

    return {
        type: 'prop', 
        subtype: 'equiv', 
        sub1: p1, 
        sub2: p2
    };
}
```

上から順番に否定、連言、選言、含意、同値の論理結合子によって組み立てられる数学的言明を作る関数です。

特に難しいことは何もしていません。

引数が適切なものでない場合には例外を投げます。

また、`subtype`プロパティの値である`neg`、`conj`、`disj`、`imp`、`equiv`はそれぞれ否定、連言、選言、含意、同値の論理結合子によって組み立てられた数学的言明であることを示しています。

なお、`sub`、`sub1`、`sub2`プロパティにより、新たに作成されたオブジェクトの材料となったオブジェクトにアクセスできるようにしています。

### プログラムによる数学的言明の作成

これで数学的言明をプログラムで作成することができるようになりましたので、適当な数学的言明を作成してみましょう。

たとえば、<code>P<sub>1</sub> → P<sub>2</sub></code>という言明は下のようにすれば作成できます。

```js
var p1 = create_implication(create_proposition(1), create_proposition(2));
```

また、<code>(P<sub>1</sub> ∧ ￢P<sub>2</sub>) ∨ P<sub>3</sub></code>という言明は下のようにすれば作成できます（なお、`(`、`)`の使い方は普通の数式における場合と同じです）。

```js
var p2 = create_disjunction(create_conjunction(create_proposition(1), create_negation(create_proposition(2))), create_proposition(3));
```

しかし、毎回プログラム上で数学的言明を作らなければならないのは面倒ですし、プログラム自体`create`から始まる関数の呼び出しの複雑な組み合わせとなるので分かりにくいです。

なので、単純なコマンドを呼び出すことで自由に数学的言明を組み立てていけるような仕組みを考えます。

### コマンドによる数学的言明の作成

まず、`math.js`というファイルに上で作成した関数を全て格納してください。

更に下のような数学的言明を表すオブジェクトをHTML形式で出力する関数を追加します。

```js
function tohtml (obj) {
  if (obj.typr === 'prop') {
    if (obj.subtype === 'atom') {
      return 'P' + '<sub>' + obj.index + '</sub>';
    }
    else if (obj.subtype === 'neg') {
      return '( ' + '￢' + tohtml(obj.sub) + ' )';
    }
    else if (obj.subtype === 'conj') {
      return '( ' + tohtml(obj.sub1) + ' ∧ ' + tohtml(obj.sub2) + ' )';
    }
    else if (obj.subtype === 'disj') {
      return '( ' + tohtml(obj.sub1) + ' ∨ ' + tohtml(obj.sub2) + ' )';
    }
    else if (obj.subtype === 'imp') {
      return '( ' + tohtml(obj.sub1) + ' → ' + tohtml(obj.sub2) + ' )';
    }
    else if (obj.subtype === 'equiv') {
      return '( ' + tohtml(obj.sub1) + ' ↔ ' + tohtml(obj.sub2) + ' )';
    }
    else {
      throw new Error('not supported subtype.');
    }
  }
  else {
    throw new Error('not supported type.');
  }
}
```

そして、下のようなHTMLページを用意します。このHTMLページは`math.js`を保存したのと同じフォルダに`main.html`というファイル名で保存することにしましょう。

```html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>形式化数学</title>
</head>
<body>
  <script src="asset/lib/vue-2.5.13/vue.min.js"></script>

  <div id="main">
    <input type="text" v-model="input" @keydown.enter="execute_command">
    <button @click="execute_command">実行</button>
    <div v-for="output in outputs" :key="output.id">
      <span v-html="output.html"></span>
    </div>
  </div>

  <script src="math.js"></script>
  <script src="main.js"></script>
</body>
</html>
```

また、上のHTMLページではVue.jsを利用しますのでVue.jsも準備してください。`math.js`を保存したフォルダの`asset`フォルダの`lib`フォルダの`vue-2.5.13`フォルダに`vue.min.js`ファイルを格納します。

最後に、`main.js`という下のような内容のJavaScriptファイルを`math.js`を保存したのと同じフォルダに用意します。

```js
var id = 0;

var context = {
  pindex: 0,
  sindex: 0,
  ss: new Map()
};

function execute_command (context, command) {
  var cs = command.split(' ');

  if (cs[0] === 'prop') {
    context.pindex++;
    var p = create_proposition(context.pindex);

    add_object(context, p);

    return p;
  }
  else if (cs[0] === 'neg') {
    var objs = get_n_objects_from_args(context, 1, cs);
    var p = create_negation(objs[0]);

    add_object(context, p);

    return p;
  }
  else if (cs[0] === 'conj') {
    var objs = get_n_objects_from_args(context, 2, cs);
    var p = create_conjunction(objs[0], objs[1]);

    add_object(context, p);

    return p;
  }
  else if (cs[0] === 'disj') {
    var objs = get_n_objects_from_args(context, 2, cs);
    var p = create_disjunction(objs[0], objs[1]);

    add_object(context, p);

    return p;
  }
  else if (cs[0] === 'imp') {
    var objs = get_n_objects_from_args(context, 2, cs);
    var p = create_implication(objs[0], objs[1]);

    add_object(context, p);

    return p;
  }
  else if (cs[0] === 'equiv') {
    var objs = get_n_objects_from_args(context, 2, cs);
    var p = create_equivalence(objs[0], objs[1]);

    add_object(context, p);

    return p;
  }
  else {
    throw new Error('not supported command.');
  }
}

function add_object (context, obj) {
  context.sindex++;
  context.ss.set(context.sindex, obj);
}

function get_n_objects_from_args (context, n, cs) {
  check_num_arguments(cs, n);

  var objs = [];
  for (var i = 0; i < n; i++) {
    var idx = get_int_argument(cs[1 + i]);
    check_in_range(idx, context.sindex);
    var obj = context.ss.get(idx);
    objs.push(obj);
  }

  return objs;
}

function check_num_arguments (cs, num) {
  if (cs.length < num + 1) {
    throw new Error('missing argument.');
  }
}

function get_int_argument (arg) {
  var i = parseInt(arg);
  if (isNaN(i)) {
    throw new Error('not integer.');
  }
  return i;
}

function check_in_range (arg, max) {
  if (arg < 1 || arg > max) {
    throw new Error('out of range.');
  }
}

var vm = new Vue({
  el: '#main',
  data: {
    input: '',
    outputs: []
  },
  computed: {},
  methods:  {
    execute_command: function () {
      var command = this.input;
      this.input = '';

      try {
        var obj = execute_command(context, command);
        if (obj != undefined && obj != null) {
          id++;
          this.outputs.push({
            id: id,
            html: '[' + id + ']' + tohtml(obj)
          });
        }
      }
      catch (err) {
        id++;
        this.outputs.push({
          id: id,
          html: err.message
        });
      }
    }
  }
});
```

このファイルでは主にユーザーが入力したコマンドを解釈して適切な数学的言明を作成し、出力する処理を行っています。

また、作成された数学的言明にはそれぞれに識別するための番号が付与され、作成した数学的言明を参照するにはこの番号を使用します。

コマンドは下のようにしました。

* `prop`コマンド・・・メタ変数を作成するコマンドです。引数はありません。自動的に新しいメタ変数が作成されます。
* `neg`コマンド・・・否定の論理結合子による数学的言明を作成するコマンドです。引数を1つ取り、この引数が示す数学的言明を材料とする新しい数学的言明を作成します。
* `conj`コマンド・・・連言の論理結合子による数学的言明を作成するコマンドです。引数を2つ取り、この引数が示す数学的言明を材料とする新しい数学的言明を作成します。
* `disj`コマンド・・・選言の論理結合子による数学的言明を作成するコマンドです。引数を2つ取り、この引数が示す数学的言明を材料とする新しい数学的言明を作成します。
* `imp`コマンド・・・含意の論理結合子による数学的言明を作成するコマンドです。引数を2つ取り、この引数が示す数学的言明を材料とする新しい数学的言明を作成します。
* `equiv`コマンド・・・同値の論理結合子による数学的言明を作成するコマンドです。引数を2つ取り、この引数が示す数学的言明を材料とする新しい数学的言明を作成します。

たとえば、`prop`コマンドを実行すると、下のような数学的言明が作成されます。

```
[1]P1
```

ここで、`[`、`]`で囲まれた数値は数学的言明の番号を表します。

この数学的言明を材料として新しい数学的言明を作成したい場合にはこの番号をコマンドの引数として指定します。

たとえば、`neg 1`コマンドを実行すると、下のような数学的言明が作成されます。

```
[2]( ￢P1 )
```

また、`imp 1 2`コマンドを実行すると、下のような数学的言明が作成されます。

```
[3]( P1 → ( ￢P1 ) )
```

このようにして、コマンドを使用して自由に数学的言明を作成することができます。

今回はここまでにしたいと思います。次回は数学的な議論の大きな部分を構成する証明について説明し、プログラム上で実装したいと思います。