JavaScript
ポエム

コードに落としこんで 哲学やろうぜ その2 ~海賊王になる方法~


概要

哲学は 日本語で書くからわかりにくい。JavaScript にすれば(少なくとも)プログラマには多少はマシになるだろう、という試み。

第一回 がわりと好評だったので、続編をつくった。内容のつながりはない。今回は帰納の正当性に関するトピック「グルー推論」をJavaScript で実装しながら、その意義を考察する。

最後まで読むと、「明日 目が覚めたら海賊王になれるはず」という信念を持てるようになれるかもしれない。毒虫になる危険性に怯えるかもしれないが。


グルーとブリーン

ブルー(つまり青)とグリーン(つまり緑)という言葉がある。合体させると二つの造語が生まれる。それがグルーとブリーン。定義は以下:

グルーの定義: 「2019年5月1日0:00 以前に観察されたことのある緑色のもの」もしくは「2019年5月1日0:00 以降に初めて観察された青色のもの」に当てはまる性質

ブリーンの定義: 「2019年5月1日0:00 以前に観察されたことのある青色のもの」もしくは「2019年5月1日0:00 以降に初めて観察された緑色のもの」に当てはまる性質

もし今が平成なら エメラルドを採掘したときに それが緑ならグルーだし、青ならブリーンだ。近い将来 新元号の下でエメラルドを採掘して、それが緑ならブリーンだし、青ならグルーだ。


いきなり何言ってんの?

グルーとブリーンの定義を見て、「意味は(たぶん)わかるが、意義がわからない」というのが一般的な反応。もちろん哲学者が 言葉遊びでこんなことを考えているわけではない。この話を続けていくと、興味深い考察が得られる。それをコードを使って納得するのが本稿の趣旨。

興味深い考察: 主張 $S_1$「平成が終わっても新発掘されるエメラルドは緑色だ」は、事実 $F_1$「これまで観察されたエメラルド全部が 例外なく緑色であった」からある程度正当化される。だがよく考えてほしい。$F_1$ とは 事実 $F_2$「これまで観察されたエメラルド全部が 例外なくグルーであった」であるともいえるではないか。ならば、主張 $S_2$「平成が終わっても新発掘されるエメラルドはグルーだ」もある程度正当化できるはずだ。そして、$S_2$ は、主張 $S_2'$「平成が終わった後に発掘されるエメラルドは青色である」と同値だ。以上、$S_1$ と $S_2'$ からいえるのは 過去例外なくエメラルドが緑色だったことを根拠とするならば、新発掘されるエメラルドの色に関して、緑だと主張するのと同じ程度の確度で青であると主張しても構わないということだ。(注:これを拒否するなら別の形での問題提起になる。「なぜ緑に関する帰納的推論は妥当と感じられるが、グルーに関する帰納的推論は妥当と感じられないのか?両者の違いは何か?」)。もっというと、何色であると主張してもそれはある程度正当化されるだろう


グルー双対定理

ちょっと考えればわかる性質を書いておく

グルーの定義(再掲): 「2019年5月1日0:00 以前に観察されたことのある緑色のもの」もしくは「2019年5月1日0:00 以降に初めて観察された青色のもの」に当てはまる性質

ブリーンの定義(再掲): 「2019年5月1日0:00 以前に観察されたことのある青色のもの」もしくは「2019年5月1日0:00 以降に初めて観察された緑色のもの」に当てはまる性質

緑と同値な表現: 「x が緑である」は以下と同値:「2019年5月1日0:00 以前に観察されたことのあるグルーなもの」もしくは「2019年5月1日0:00 以降に初めて観察されたブリーンのもの」

青と同値な表現: 「x が青である」は以下と同値:「2019年5月1日0:00 以前に観察されたことのあるブリーンなもの」もしくは「2019年5月1日0:00 以降に初めて観察されたグルーのもの」

僕らは冒頭で青と緑をつかってグルーとブリーンを定義したわけだけど、グルーとブリーンを使って青と緑が定義できると再解釈することもできる。


設計のようなもの

オブジェクトに複数のフィールドを用意しよう



  • colorフィールド: オブジェクトの色 RGB値を表す三つ組。例: [255, 255, 0] なお、観測していない場合は undefined が入る(未観測のオブジェクトの時など)。


  • oDateフィールド: 観測日時を表す Dateオブジェクト。


  • isBlueフィールド: Boolean値。青ならフラグが立つ


  • isGreen フィールド: Boolean値。ミドリならフラグが立つ


  • isGrueフィールド: Boolean値。グルーならフラグが立つ


  • isBleenフィールド: Boolean値。ブリーンならフラグが立つ

フラグは独立でないので不整合を避けるのはプログラマの責務(例えば isBlueisGreen は同時にフラグが立ってはいけない)。フラグでなく都度計算する関数にすれば?と思うかもしれないが、colorundefined でもフラグを立てることができる場合がある(次節グリーン判定法参照)のでこうする必要がある。


コーディングの前の注意点

平成に採掘したエメラルドの扱い: たった今、発掘したエメラルドに 0 という名前を付けよう。0 は緑色だしグルーだ。この事実は新元号の下でも変わらない。つまり新元号の下でも 0 は緑色だしグルーだ。よくある間違いは、新元号の下で 0 はグルーではなくブリーンだというものである。だがそれは間違えている。一度でもグルー判定されたらグルーのままなのだ(定義をよく読んで!)。一方、新元号の下で発掘されたエメラルドに 1 という名前を付けよう。もし 1が緑色ならば、1 は緑色でブリーンだ。

グリーン判定法: あるオブジェクト obj が緑であると確定する方法は(少なくとも)二つある。一つ目は obj.color[0, 255, 0] であると確かめること。もう一つは、グルー双対定理を使うことだ。つまり obj.colornull であっても、「obj.oDate が平成で obj.isGrue === true」 もしくは「obj.oDate が新元号で obj.isBleen === true」であれば緑であると判断できる。内部のフラグの制御をしっかりして 不整合を起こさないようにしないといけない 責務がプログラマにある


コーディング

エメラルドクラスの仕様と実装: 「観測日時が xxx でその時の RGBが yyyなエメラルドを考えよう」 というのが典型的なユースケース。ただ、「観測日時が xxx で isGrue フラグが立ったエメラルドを考えてみよう」とか「エメラルド x の isGrue フラグを立ててみよう」みたいなケースがあるので、それを受け止められるように柔軟な設計にする必要がある。

使い方=仕様から見た方がいいかな。コンストラクタに渡すオブジェクトが色々変わるという点に注意して仕様を決めよう。まずは普通のエメラルドインスタンスの生成方法。


Emeraldクラスの使い方その1.js

const T = new Date('2019-05-01'); // 平成と新元号の境目

const T1 = new Date('2019-01-01'); // 平成は T1 で代表させる
const T2 = new Date('2019-08-01'); // 新元号は T2 で代表させる

const GREEN = [0, 255, 0];
const BLUE = [0, 0, 255];

const a = new Emerald({ oDate: T1, color: [...GREEN] });
const b = new Emerald({ oDate: T1, color: [...BLUE] });
const c = new Emerald({ oDate: T2, color: [...GREEN] });
const d = new Emerald({ oDate: T2, color: [...BLUE] });

[a, b, c, d].forEach(eme => eme.print());
/* output
<Emerald: T1, GREEN GRUE >
<Emerald: T1, BLUE BLEEN >
<Emerald: T2, GREEN BLEEN >
<Emerald: T2, BLUE GRUE >
*/


続いてフラグを指定する生成方法:


Emeraldクラスの使い方その2.js

const x1 = new Emerald({ oDate: T1, isGreen: true });

const x2 = new Emerald({ oDate: T1, isBlue: true });
const x3 = new Emerald({ oDate: T1, isGrue: true });
const x4 = new Emerald({ oDate: T1, isBleen: true });

[x1, x2, x3, x4].forEach(eme => eme.print());
/* output
<Emerald: T1, GREEN GRUE >
<Emerald: T1, BLUE BLEEN >
<Emerald: T1, GREEN GRUE >
<Emerald: T1, BLUE BLEEN >
*/

const y1 = new Emerald({ oDate: T2, isGreen: true });
const y2 = new Emerald({ oDate: T2, isBlue: true });
const y3 = new Emerald({ oDate: T2, isGrue: true });
const y4 = new Emerald({ oDate: T2, isBleen: true });

[y1, y2, y3, y4].forEach(eme => eme.print());
/* output
<Emerald: T2, GREEN BLEEN >
<Emerald: T2, BLUE GRUE >
<Emerald: T2, BLUE GRUE >
<Emerald: T2, GREEN BLEEN >
*/


最後に、変な使い方:


Emeraldクラスの使い方その3.js

const z = new Emerald({ oDate: T1 });

z.props.isBlue = true;
z.rebuild();
z.print(); // -> <Emerald: T1, BLUE BLEEN >

実装は末尾に記載。


デモンストレーション!

ではコードで、先述の「興味深いトピック」を再現してみよう。

平成某日。エメラルド鉱夫である僕は、100個のエメラルドを採掘することに成功した。いつもより 多めに採掘できたことに満足をしつつも、若干の退屈を覚えている。100 個のエメラルドを検査ボックスにいれ、全て RGB値が (0,255,0) であることを確認し、検品スタンプを押す。今日の仕事はこれで終わりだ:

// 100 個のエメラルドを配列で保持

const emeralds = new Array(100).fill(0).map(() => new Emerald({ oDate: T1, color: [0, 255, 0] }));

腰をおろして休憩していると ネル先輩がやってきた。「仕事お疲れ。100個も採掘したなんてすごいな」という先輩に、僕は答える「いくら掘っても給料は変わりませんけどね。青色のエメラルドがでてくればボーナスが出るかもしれませんけど。」

// 当然であるが、100個のエメラルドは全て緑色だ

const fact1 = emeralds.every(x => x.props.isGreen); // -> true

ネル先輩は 急に真顔になった。「ここだけの話だけどな。明日採掘するエメラルドは青色だって話がある。早めに来て全部かっさらっちまったほうがいいんじゃないか?」僕は答える「冗談はよしてくださいよ。今日も昨日も一昨日も全部エメラルドは緑色でした。fact1を見てくださいよ。全部緑でしょ。つまらんもんです。」

先輩は真顔を崩そうとしない。「お前さ、その fact1 報告書を作成するボタンの一つ下のボタンを押してみろ。 fact2 って報告書がでてくる」。僕は言われるがままにボタンを押す。報告書が出てくる。100個の 〇がついたつまらない報告書だ。 fact1 となにも変わりやしない。

const fact2 = emeralds.every(x => x.props.isGrue);

僕はいう。「何ですか、この報告書。fact1 と変わらないようにみえるんですが」。先輩の声に熱気がこもり始める。「いいか、この報告書はエメラルドのグルー性検証報告書というものだ。グルーとはな・・(略)・・。この報告書は、俺らが採掘したエメラルドのグルー性を表したものだ。確かに見た目は fact1fact2 は変わらない。しかし意味が違う。俺はこっそり、昨日の採掘分も一昨日の分の採掘分に関してもグルー性検証をやってみた。そしたらなんと、それらも全てグルー性が存在していることを示していた。」

先輩は興奮のせいか早口でまくしたてる。「俺はわかったんだよ。この鉱山のエメラルドは全部グルーなんだって。そこから帰結されることは、明日採掘するエメラルドは青色なんだよ。」

// 明日 グルー性を持つエメラルドが採掘したとしよう。それは青色だろうか?→青色だった

const X = new Emerald({ oDate: T2, isGrue: true });
console.log(X.props.isBlue) // -> true

僕は 鼓動が早まるのを感じつつも 平静を装い応える。「仮に、そうだとしてもですよ。明日いつも通り 緑かもしれないじゃないですか? fact1 報告書を信じるなら明日だって緑のはずですよ」。先輩。「それは認める。fact1報告書のいうことを信じるなら明日は緑だ。 ただ、fact2報告書を信じるなら明日は青だ。明日のエメラルドが青の可能性はせいぜい 50% といったところか。それでも大金持ちになるチャンスだぞ。早番勤務するのが正解じゃないか?」

僕の気持ちは決まった。「確かに。これは乗らないのはもったいない。ギャンブルだけど勝算がありそうです。早番で来ることにします。情報ありがとうございました、先輩」。先輩は満足そうにうなずき、帰っていった。僕も今日は早めに家に帰ろう。明日は忙しくなりそうだ。


コード

const T = +new Date('2019-05-01'); // 平成と新元号の境目

const T1 = +new Date('2019-01-01'); // 平成は T1 で代表させる
const T2 = +new Date('2019-08-01'); // 新元号は T2 で代表させる

const GREEN = [0, 255, 0];
const BLUE = [0, 0, 255];

class Emerald {
constructor (obj) {
this.props = { ...obj };
if (this.props.color) { // RGB が与えられたら最低限のフラグだけ立てて、残りは rebuild に処理させる
if (this.props.color.every((x, i) => x === GREEN[i])) {
this.props.isGreen = true;
}
if (this.props.color.every((x, i) => x === BLUE[i])) {
this.props.isBlue = true;
}
}
this.rebuild();
}

rebuild () { // フラグ処理。不整合時でも動作させるためにフラグ間に優先順位がある
if (!this.props.oDate) throw Error('oDate unspecified!');

// 緑フラグが立っている場合
if (this.props.isGreen) {
this.props.isBlue = false;
this.props.isGrue = this.props.oDate < T;
this.props.isBleen = this.props.oDate > T;
return;
}

// (緑フラグが立っておらず)青フラグが立っている場合
if (this.props.isBlue) {
this.props.isGreen = false;
this.props.isGrue = this.props.oDate > T;
this.props.isBleen = this.props.oDate < T;
return;
}

// (緑フラグ、青フラグが立っておらず)グルーフラグが立っている場合
if (this.props.isGrue) {
this.props.isBleen = false;
this.props.isGreen = this.props.oDate < T;
this.props.isBlue = this.props.oDate > T;
return;
}

// (緑フラグ、青フラグ、グルーフラグが立っておらず)ブリーンフラグが立っている場合
if (this.props.isBleen) {
this.props.isGrue = false;
this.props.isGreen = this.props.oDate > T;
this.props.isBlue = this.props.oDate < T;
return;
}
}

print () { // 表示関数
const t = this.props.oDate === T1 ? 'T1' : 'T2';
let flagStr = '';
if (this.props.isBlue) flagStr += ' BLUE ';
if (this.props.isGreen) flagStr += ' GREEN ';
if (this.props.isGrue) flagStr += ' GRUE ';
if (this.props.isBleen) flagStr += 'BLEEN ';
console.log(`<Emerald: ${t}, ${flagStr}>`);
}
}

// 今日 1000 個のエメラルドを採掘したら全部 RGB が [0, 255, 0]
const emeralds = new Array(1000).fill(0).map(x => new Emerald({ oDate: T1, isGreen: true }));
const fact1 = emeralds.every(x => x.props.isGreen); // -> true
const fact2 = emeralds.every(x => x.props.isGrue); // -> true
const X = new Emerald({ oDate: T2, isGrue: true });
console.log(fact1, fact2, X.props.isBlue);

/*
const a = new Emerald({ oDate: T1, color: [...GREEN] });
const b = new Emerald({ oDate: T1, color: [...BLUE] });
const c = new Emerald({ oDate: T2, color: [...GREEN] });
const d = new Emerald({ oDate: T2, color: [...BLUE] });
[a, b, c, d].forEach(eme => eme.print());
const x1 = new Emerald({ oDate: T1, isGreen: true });
const x2 = new Emerald({ oDate: T1, isBlue: true });
const x3 = new Emerald({ oDate: T1, isGrue: true });
const x4 = new Emerald({ oDate: T1, isBleen: true });
[x1, x2, x3, x4].forEach(eme => eme.print());
const y1 = new Emerald({ oDate: T2, isGreen: true });
const y2 = new Emerald({ oDate: T2, isBlue: true });
const y3 = new Emerald({ oDate: T2, isGrue: true });
const y4 = new Emerald({ oDate: T2, isBleen: true });
[y1, y2, y3, y4].forEach(eme => eme.print());
const z = new Emerald({ oDate: T1 });
z.props.isBlue = true;
z.rebuild();
z.print();
*/


参考

『規則と意味のパラドックス』飯田隆 ちくま学芸文庫