ネタ切れ(・ω・`)

最近ブログに書くネタが無くてですね


最後に更新してからもう半年が経とうとしているんです。切れたんですよネタが。ホント、書くことがない。


もういくら考えてもネタが思い付かなくて。


それでですね、ついに苦肉の策に出ようと思ったんですよ。


「最近ブログに書くネタが無くてですね」とブログ自体に書くことにしたんです。ネタが無いということをネタにする。メタですね。


でもそれだけじゃつまらない。単に一度メタ化するだけじゃ、ネタが無いのと同じですよ。


そこでこうすることにしたんです。


「「最近ブログに書くネタが無くてですね」とブログ自体に書くことにしたんです。ネタが無いということをネタにする。メタですね。」という文をブログに書く。つまり2重のメタ化です。


さらに、「「「最近ブログに書くネタが無くてですね」とブログ自体に書くことにしたんです。ネタが無いということをネタにする。メタですね。」という文をブログに書く。つまり2重のメタ化です。」と書くと3重にメタ化することもできます。


ここで気付くわけですね、「さらに、「「「最近ブログに書くネタが無くてですね」とブログ自体に書くことにしたんです。ネタが無いということをネタにする。メタですね。」という文をブログに書く。つまり2重のメタ化です。」と書くと3重にメタ化することもできます。」と書いた時点で既に4重のメタ化が起こっていると。


ペアノの公理ってご存じですか?あれに似ていますね。つまり任意の自然数nに対してn重のメタ化が可能となります。


おや?私は「任意の自然数nに対してn重のメタ化が可能となります。」と書いてしまいました。これをここに書くと、いったい何重のメタ化になるんでしょう。


自然数の範囲を超えてしまいましたね。じゃあこれを、「どんな自然数よりも大きい数」としてωと名付けましょう。


さて、ω重のメタ化ができました。


ん?「ω重のメタ化ができました」?これは何重のメタ化ですか?


ωの次の数。ω+1です。私はω+1重のメタ化を行ったわけです。


はい、たった今「私はω+1重のメタ化を行ったわけです。」と書くことでω+2重のメタ化が行われましたね。


そして「はい、たった今「私はω+1重のメタ化を行ったわけです。」と書くことでω+2重のメタ化が行われましたね。」と書いたのでメタ化はω+3重です。


これを先程と同様に繰り返すと、自然数nに対しω+n重のメタ化が可能です。


……んん?「自然数nに対しω+n重のメタ化が可能です」???


そうですね。ω+ω、即ちです。


です」は2ω+1。「「です」は2ω+1。」は2ω+2。「「「です」は2ω+1。」は2ω+2。」は2ω+3。これを繰り返せば2ω+nが作れます。そして「2ω+nが作れます。」はです。


これを繰り返していくと、ωと任意の自然数nの積が作れます。


おや???「が作れます」?????(しつこいですよ)


はい、ω×ωですね。


これが順序数の世界です。


ω^ωも作ってみてください。


ではまた(・ω・`)

みんな大好き,ポインタ/配列/参照 ~多次元配列まとめ~

汎整数型(論理型(bool),文字型(charなど),整数型(intやlongなど))と浮動小数点型を合わせて算術型といい,算術型とvoid型を合わせて基本型といいます。基本型からは「ポインタ型」「配列型」「参照型」の3種類の組込み型が得られますが,この記事ではその中でも特に(単なる基本型へのポインタ型,配列型,参照型を除いた)2重以上のポインタ型,配列型,参照型の扱い方について説明します。constについては説明しません。templateについては説明しません。変数の型だけを見るので,変数名は全てハンガリアン記法の接頭辞のみとします。

まずCにあるポインタ型と配列型から先に説明します。

int i;

iはint型です。関数hoge(int)を宣言しておくと,hoge(i)でhoge(int)が呼び出されます。

intに*,[1],[2]を付けるとそれぞれ次の型が得られます。

int *pi, a1i[1], a2i[2];

piはint*型,a1iはint[1]型,a2iはint[2]型です。関数呼び出しではこれらは全て同じものとして扱われます。つまり関数hoge(int*)を宣言しておくと,hoge(pi),hoge(a1i),hoge(a2i)の全てにおいてhoge(int*)が呼び出されるということです。

int*に*,[1],[2]を付けるとそれぞれ次の型が得られます。

int **ppi, *a1pi[1], *a2pi[2];

ppiはint**型,a1piはint*[1]型,a2piはint*[2]型です。関数呼び出しではこれらは全て同じものとして扱われます。つまり関数hoge(int**)を宣言しておくと,hoge(ppi),hoge(a1pi),hoge(a2pi)の全てにおいてhoge(int**)が呼び出されるということです。

int[1]に*,[1],[2]を付けるとそれぞれ次の型が得られます。

int (*pa1i)[1], a1a1i[1][1], a2a1i[2][1];

pa1iはint(*)[1]型,a1a1iはint[1][1]型,a2a1iはint[2][1]型です。関数呼び出しではこれらは全て同じものとして扱われます。つまり関数hoge(int(*)[1])を宣言しておくと,hoge(pa1i),hoge(a1a1i),hoge(a2a1i)の全てにおいてhoge(int(*)[1])が呼び出されるということです。hoge(int**)に渡したいときはキャストします。

int[2]に*,[1],[2]を付けるとそれぞれ次の型が得られます。

int (*pa2i)[2], a1a2i[1][2], a2a2i[2][2];

pa2iはint(*)[2]型,a1a2iはint[1][2]型,a2a2iはint[2][2]型です。関数呼び出しではこれらは全て同じものとして扱われます。つまり関数hoge(int(*)[2])を宣言しておくと,hoge(pa2i),hoge(a1a2i),hoge(a2a2i)の全てにおいてhoge(int(*)[2])が呼び出されるということです。

変数Aの型がTであるとき,&AはT*型です。即ちint(**[2])[3]型変数a3ppa2iのアドレス&a3ppa2iはint(**(*)[2])[3]型です。

変数Bの型がT*であるとき,*BはT型です。即ちint(*(**)[4])[2]型変数a2pa4ppiの参照*a2pa4ppiはint(*(*)[4])[2]型です。

変数を配列型にキャストすることはできません。reinterpret_castとかそういう問題ではなく,言語仕様上絶対に不可能です。

int a0i[0]はint[0]型です。

sizeof iが4,sizeof piが8であった場合,sizeof a1iは4×1=4,sizeof a2iは4×2=8,sizeof ppiは8,sizeof a1piは8×1=8,sizeof a2piは8×2=16,sizeof pa1iは8,sizeof a1a1iは4×1×1=4,sizeof a2a1iは4×1×2=8,sizeof pa2iは8,sizeof a1a2iは4×2×1=8,sizeof a2a2iは4×2×2=16です。配列+整数のsizeof,即ちsizeof(a2i+1)やsizeof (a2a1i+0)などは全てsizeof pi(=8)に等しくなります。sizeof *a2a1iは*a2a1iがint[1]型なのでsizeof a1i(=4)に等しくなります。sizeof *(a2a1i+0)も同じくsizeof a1iに等しくなります。a2a1i[0]は*(a2a1i+0)の糖衣構文なので,sizeof a2a1i[0]も同じくsizeof a1iに等しくなります。

C++にはポインタ,配列に加え,参照というものがあります。

関数hoge_cpp(int&)をhoge_cpp(i)で,関数hoge_cpp(int&&)をhoge_cpp(i+0)で,関数hoge_cpp(int*&)をhoge_cpp(pi)で,関数hoge_cpp(int*&&)をhoge_cpp(pi+0)やhoge_cpp(a1i)やhoge_cpp(a1i+0)で,関数hoge_cpp(int(&)[1])をhoge_cpp(a1i)で,関数hoge_cpp(int(&)[2])をhoge_cpp(a2i)で呼び出せます。hoge_cpp(a1i)でhoge_cpp(int(&)[2])を呼び出すことはできないので,テンプレートを使って配列の大きさごとに関数を作れば(使うたびに関数が増えますが)関数内で配列の大きさを知ることができます。

とりあえずこの記事はここまでです。部分的に情報密度が高くなってしまいましたが,その一部でもあなたの役に立てば幸いです。

プログラマーじゃなくても分かる,Krileのクエリの書き方

twitterを本気でやるならやっぱりKrile Starryeyesを使うのが一番。

セットアップの仕方は以下のサイトに書かれています。

Krile StarryEyesのセットアップの仕方 - -

Krileではタブというものがあり,「自分がフォローしている人のツイートを表示するタブ」「自分宛てのリプを表示するタブ」「自分の名前が含まれるツイートを全て表示するタブ」など,一つ一つのタブに表示するツイートを指定することができます。

しかし指定するための「クエリ」の書き方が分からない…!!という人も多いのではないでしょうか。

実はクエリの書き方は次のサイトに全て書かれています。

KQ_Overview · karno/StarryEyes Wiki · GitHub

しかしこれ…文体がプログラマー向けなんですよね。

まぁクエリを書くのはプログラミングと同じなので仕方ない。

でもKrileを使う人がみんなプログラマーというわけではありません。なのでここでは,プログラマーでない人のためにKrileのクエリの書き方を説明したいと思います。

(おっと…プログラミングが小学校で義務教育化されるんだっけ?まぁそうなったらこういう記事の需要も無くなっていくのでしょうね…)

クエリの構成

Krileのクエリは前半部分と後半部分に分かれています。前半が「from」で始まる「ソースクエリ」,後半が「where」で始まる「フィルタクエリ」です。例えばクエリが「from local where text contains "love"」なら「from local」がソースクエリで,「where text contains "love"」がフィルタクエリです。

ソースクエリではツイートを取得する範囲を指定し,そのうちフィルタクエリで指定した条件を満たすものだけがタブに表示されます。

例えばソースクエリでは「タイムラインに流れてくる全てのツイート」「検索でヒットした全てのツイート」「特定の人の全てのツイート」などが指定できて,フィルタクエリでは「本文中に~を含む」「~RT以上されている」「リスト~に登録されていてIDに数字0を含まない人のうち~人以上にいいねされている」などが細かく指定できます。

ソースクエリ

fromの後に書ける単語がいくつか決まっています。

local (allと書いても同じ)

「from local」とすると,Krileを使っている間に一度でも保存されたツイート全てが対象になります。タイムラインやリプライなど自分に関連しているツイートならFF内外は関係ないのですが,twitter上のツイートが全て検索できるわけではありません。

「from local: ~」のように,コロン「:」で区切ってその後にKrileのタブ名を指定すると,そのタブ内にある全てのツイートが対象になります。*1

 home

「from home」とすると,タイムライン(フォローしている人のツイート)が対象になります。

複数のアカウントを使っている場合は,「from home: "57tggx"」のように,コロン「:」で区切った後「"」2つで囲った部分にIDを書くと,そのアカウントのタイムラインだけが対象になります。

 mention (mentions, reply, repliesも全て同じ)

自分宛の返信が対象になります。複数のアカウントを使っている場合はhomeと同じように「from mention: "ID"」でアカウントを指定します。

message (messages, dm, dmsも全て同じ)

自分宛のDMと自分が送ったDMが対象になります。複数のアカウントを使っている場合はhomeと同じように「from message: "ID"」でアカウントを指定します。

list

指定したリストのツイートが対象になります。「/」で区切って「from list:"自分のID/リスト名"」と書きます。他の人の作ったリストの場合は,自分の持っているアカウントから一つ選んで,「from list:"自分のID/リスト作成者のID/リスト名"」とします。

search (findも同じ)

「from search:"~"」とするとtwitter検索でヒットした全てのツイートが対象になります。普通のtwitter検索とほぼ同じですが,「"そんなの聞いてない"」のような完全一致検索をするときは中の「"」を「\"」(円マーク+「"」)に書き換えて「from search:" \"そんなの聞いてない\" "」とします。

track (streamも同じ)

「from track:"~"」でもツイート検索ができます。こちらはストリーム検索といって,検索結果に新しいツイートが自動で読み込まれ続けるため,サーバに負荷がかかりません。

conv (conversation, talk, treeも同じ)

twitterでは,全てのツイートにID番号が振られています。「from conv:"01234567890123456789"」のようにIDを指定すると,そのツイートとそれに対する全ての返信(リプライツリー)が対象になります。

user

特定の誰かに張り付くならこれ。「from user:"57tggx"」でIDを指定するか,「from user:"#808612487507513344"」(「#」を忘れないこと)で固有IDを指定します。

フィルタクエリ

フィルタクエリを省略すると,取得した全てのツイートがそのまま表示されます。

フィルタクエリには色々な文が書けます。

文の例とその意味,同じと見なせる単語は次の通りです:

text="334" 本文が「334」と一致する textとbody, =と==
text->"334" 本文に「334」が含まれる ->とcontains
text startswith "334" 本文が「334」で始まる startswithとstartwith
text endswith "334" 本文が「334」で終わる endswithとendwith
text->"windows" 本文に「windows」が含まれる(大文字小文字を区別しない)  
caseful text->"windows" 本文に「windows」が含まれる(大文字小文字を区別する)  
favs=3 ふぁぼ数が3 favsとfavorites, favorer, favorers
favs>3 ふぁぼ数が3より多い  
rts>3 RT数が10より多い rtsとretweets, retweeters
rts>favs RT数がふぁぼ数より多い  
rts*rts>favs*2+5 RT数の2乗がふぁぼ数の2倍+5より多い  
rt リツイートである*2 rtとretweet, isretweet, is_retweet
user=@57tggx ツイート主が@57tggx  
user="57tggx" ツイート主のIDが57tggx*3 userとuser.screen_name
user->"tg" ツイート主のIDに「tg」が含まれる  
user=808612487507513344 ツイート主の固有IDが808612487507513344*4 userとuser.id
user.status>100 ツイート主のツイート数が100より多い*5 .statusと.statuses, .statuscount, .status_count, .statusescount, .statuses_count
user.following>100 ツイート主のフォロー数が100より多い .followingと.friend, .friends, .followings, .friendscount, .friends_count, .followingscount, .followings_count
user.follower>100 ツイート主のフォロワー数が100より多い .followerと.followers, .followerscount, .followers_count
user.fav>100 ツイート主のふぁぼ数が100より多い .favと.favs, .favcount, .favorite, .favorites, .favscount, .favs_count, .favoritescount, .favorites_count
user.list>100 ツイート主のリスト被登録数が100より多い .listと.listed, .listcount, .list_count, .listedcount, .listed_count
user.screenname="57tggx" ツイート主のスクリーンネームが「57tggx」 .screennameと.screen_name
user.name->"ビタリスト" ツイート主のハンドルネームに「ビタリスト」が含まれる .nameと.username
user.bio->"数ぽよ" ツイート主のプロフィールに「数ぽよ」が含まれる .bioと.desc, .description
user.loc="どこでもない" ツイート主の設定所在地が「どこでもない」 .locと.location
user.lang="en" ツイート主の設定言語が「en」(=英語) .langと.language
user.protected 鍵アカウント .protectedと.isprotected, .is_protected
via->"twitter web client" twitter web clientから投稿されている*6 viaとfrom, source, client

数字に対して使える記号は次の通りです:「+」(加),「-」(減),「*」(乗),「/」(除),「=」(イコール, 「==」も同じ),「<」(小なり),「<=」(小なりイコール),「>」(大なり),「>=」(大なりイコール),「!=」(ノットイコール)。

複数の条件を重ねるときは「&」「|」を使います。例えば「user.following > 100 | user.follower > 100」は「フォロー数が100より多い」「フォロワー数が100より多い」の少なくとも一方を満たしているという意味で,「user.protected & user.status<=100」は「鍵アカウント」「ツイート数が100以下」の両方を満たしているという意味です。「&&」は「&」,「||」は「|」と同じ意味です。

条件を否定するには「!」を使います。「!user.protected」は「鍵アカウントでない」という意味です。

優先順位は括弧で指定します。「!(text->"57tggx" & user.name!="57tggx")」は「「「text->"57tggx"」かつ「user.name!="57tggx"」」でない」という意味になります。

 ここまでで大体のことはできると思います。とりあえずこの記事はこれで終わりですが,興味があったらこの先を自分で調べてみて下さい。

*1:複数のタブに同じ名前を付けることができますが,そのときは指定した名前を持つタブのうち1つのみが対象となります

*2:Krileではリツイートが元ツイートと別に扱われます。フィルタにrtを指定すると,元ツイートは表示されずリツイートのみが表示されます。

*3:「@57tggx」と「=」で結ばれたときの「user」と,「"57tggx"」と「=」で結ばれたときの「user」は微妙に違う意味になります。前者は「ユーザオブジェクト」,後者は「文字列」です。userはもともとユーザオブジェクトですが,文字列と比較しようとしたときだけIDを表す文字列になります

*4:数値と比較しようとすると,今度は固有IDを表す数値になります

*5:ユーザオブジェクトの後に「.~」と続けることでそのアカウントに関する情報が得られます

*6:正確には「via名の中に"twitter web client"という文字列が含まれている」ですが,「->」を「=」に変えるとなぜか上手くいきません

俺的名付けメモ

#define ARC(i,a,b) for(Int i=(a);i<(b);++i)
#define ARCH(i,a,b) for(Int i=(a)-1;i>=(b);--i)
#define RC(i,n) ARC(i,0,n)
#define RCH(i,n) ARCH(i,n,0)
#define SRC(i,s) for(Int i=0;(s)[i];++i)
#define ATH(a,b) ( (a)>(b)?(a):(b) )
#define OTH(a,b) ( (a)<(b)?(a):(b) )
#define ANTH(a,b) {if( (a)<(b) )(a)=(b);}
#define ONTH(a,b) {if( (a)>(b) )(a)=(b);}
#define ANT printf
#define ENT scanf
#define AT putchar
#define ET getchar
#define _ AT('\n');

 

変数名の付け方

ループ内

和:sum

~の和:_sum

~の個数:_cs

条件下でインクリメントする変数:count

最初trueにしておいて条件下でfalseにする変数:bant

毎回読み込んで捨てる変数:thon

謎の書きかけ小説メモ

私は既にもう何時間も砂漠を歩き続けていた。足元をかさかさと歩く虫のような虚数や,時折生えている枯れそうな行列にももう慣れた。ずっと前にオアシスで汲んだ集合が水筒の中にあと少し残っていたはず…背負ったリュックを地面に下ろして開くと,関数に吹かれて飛んできた変数がリュックの中に入り込んだ。水筒の中身はあと僅かだった。集合…包含関係…平面図示,境界含む,含まない…。集合を節約しつつ一通りの証明を終え,私は一時的に喉を満たした。

私が迷い込んでしまったこの世界は,なにもかもが数学らしい。間違いのないように一つ一つ証明を確かめながら行動しないといけないため,何をするにも頭が疲れてしまう。というか私はもう気が狂いそうだ。だから一刻も早くこの世界から抜け出すために,私は今歩いているのだ。

私は歩き続けた…そして遂に,この世界の出口に繋がっているとされる巨塔,自然数にたどり着いたのだった。

私は自然数の入り口の前に立った。地面と同じ高さに1階があり,任意の階の上に「上の階」がある。2つの階が異なるならば,その「上の階」も異なる。1階の上には2階,2階の上には3階があり,それが永遠に続いていた。自然数は高く,上の方は当然雲に隠れて見えないのだが,近くで見ると意外と横にも大きかった。高さ加算無限の塔を支えるのだから当然だろう。

私は乗法単位元の定義を確認した上で,石の扉をゆっくりと開けた。

1階。ずっと一人で砂漠を彷徨っていた私は,そこに人がいるのに驚いた。壁にもたれかかって煙草をふかしていた,もう何日も風呂に入っていなさそうな男たちが,そろって私を見た。

「あんた,どこから来た」

「ええと,なんか迷い込んじゃって…気付いたらここに…」

「ふうん?」

彼らはそれ以上私に興味を示さなかった。

私は数1のもつ性質をおさらいしながら,薄暗い空間をまっすぐ進んでいった。任意の整数は1の倍数…即ち1を約数にもつ。1は素数ではない…1が素数だと定義しても何のメリットも得られないだろう,素因数分解の一意性が失われてしまうのが最大の欠点だろうか。1のa乗はaに関わらず1…aは整数,実数,複素数,いや今後どんな数学的概念を考えるとしても,そこに累乗という名の付いた演算を定義するならばおそらく1のa乗は1とするだろう。

しばらく歩き続けると,私の前に大きな壁が立ちはだかった。壁沿いに歩くと何かあるはずだ。右に行くか左に行くか少し迷ったが,どちらでも大して変わらないはずだと思い,右に行くことにした。そうして壁伝いにまたしばらく歩き,ついに壁の向こうに繋がるドアを見つけた。

ドアを開けると,その向こうにはまぶしい世界が広がっていた。老若男女たくさんの人々が楽しそうに笑って話していた。

「平方のmodをとるとさーww」

「あれ左辺は2の倍数じゃん?」

人種もさまざまだった。白人,黄色人,黒人が何の抵抗も無く語り合っていた。数学には国籍も人種も関係ない…平和な世界の一端が垣間見えた気がした。

そのとき,おいしそうないい匂いが漂ってきて,私はもう何日も集合しか口にしていないことに気付いた。空いていた近くの椅子に座って,テーブルの上の料理に伸ばした手が止まった…ええと,なんというか得体の知れないものだった。なんだコレ…累乗と割り算を含む式…が,整数となるような…組を全て求めよ…?私はその料理に全く手が付けられなかった。その横に添えてある小さな実だけでも食べようとして口に入れたがしかし…n以下でnと互いに素な正整数の個数…が,nと互いに素な正整数aの右肩に指数として乗っかっていて…nを法として1と合同…?全く,歯が立たなかった。何か他に,食べられそうなものは無いか…。辺りを見回しても,どのテーブルに置いてある料理も見たことの無いものばかり。私がおろおろしていると,一人の女性がやってきて私がさっき食べようとした固い実を手にとり,

「ん,たまにこの味が懐かしくなるのよねー」

そう言っておいしそうにかみ砕いた。私の唖然とした視線に気付いた彼女に

「どうしたの?」

と尋ねられ,私はしどろもどろになりながらやっと答えた。

「あ,あの…今のそれ,どうやって…ったんですか…?」

「ん,今のって…ああ,ファイ関数のやつ?」

「ファイ…関数…」

n以下でnと互いに素な正整数の個数…何に使うんだかよく分からないようなその関数には,ファイという名が付いていた。

「あなたここに来たばかりなのね。いいわ,教えてあげる。オイラーの定理と言う重要な定理よ」

定理1. (オイラーの定理)

nとaを互いに素な正の整数とするとき, a^φ(n)≡1 (mod n) (ただしφ(n)はn以下でnと互いに素な正整数の個数)が成り立つ。

C++の歴史

注. この記事は学校の課題で書いたレポートの内容を写したものです。

C++の誕生

C++を考案したのは, デンマーク人のビャーネ・ストラウストラップである*1[1]. 彼はケンブリッジ大学計算機研究所で, 博士論文のために複数のコンピュータに仕事を分散させて組織化する方法を研究しており, コンピュータ間の複雑な通信を再現する大規模なシミュレーションをSimulaというプログラミング言語を使って行っていた. Simulaは1960年代に作られた世界初のオブジェクト指向プログラミング言語であり, 名前の通りシミュレーションに適していた. 当時の他の言語と異なり, 全体のプログラムを小さなプログラムの集合体として作るという方式をとっていたため, 一つの大きなプログラムを作る方式より理解しやすく修正しやすいコードが簡単に書けた. 他にもバグを抑える包括的型チェックなどSimulaには様々な利点があったのだが, 残念なことに動作が重く, 大きなプログラムをコンパイル・リンクするのに膨大な時間がかかったため, 実際にシミュレーションを行ってデータをとることができそうになかった. このままではプロジェクトが中断してしまう. そこでビャーネは別の言語BCPLを使った. BCPLは1966年に作られたプログラミング言語であり, 後のC言語の元になったB言語の元になった言語である. シミュレーションに適していないBCPLでコードを書くのは大変に骨の折れる作業であった. 最終的にはなんとか機能するプログラムを作ることができたが, ビャーネは適切な道具・・・・・を使わずに難題に取り組むような真似を二度としてはいけないという教訓を得たのであった.

ケンブリッジ大学を去った後, ビャーネは早速適切な道具・・・・・を作ることにした. 結論から言うとそれはSimulaの機能を引き継いだ新しいプログラミング言語である. 彼は"A History of C++: 1979-1991"([2])の中で「良い道具」について次のように述べている:

  1. 良い道具はSimulaのプログラム構造 --- 即ち, クラス, 何らかの形のクラスの階層構造ヒエラルキー, 何らかの形による並行処理コンカレンシーのサポート, そしてクラスに基づく強い(=静的スタティックな)型検査機構タイプチェックシステムを持っているべきである. プログラム言語は, プログラムを実装するためだけのものではなく, 発明やデザインの過程を手助けするものであると, 私は見なしているのである.
  2. 良い道具は, BCPLと同じくらい高速なプログラムを作る能力を有し, BCPLの分割コンパイルされたユニットを結合する能力を引き継ぐべきである. C, Algol68, Fortran, BCPL, アセンブラなどの言語で書かれたユニットを一つのプログラムにまとめるためには, 簡単な統一規則が必要であり, そのため, どれか一つの言語固有の制限に捕らわれないようにしなければいけない.
  3. 良い道具は, 移植性の高い実装をも可能にするべきである. 私のこれまでの経験では, 私が「良い」機能を必要としていても, それが実装されるのは大抵その「翌年」であり, さらに実装されたとしても私の手に入りうる環境では動かなかった. つまり, 機能は多数の環境で実装されるべきであり(独占は特殊な環境のユーザや貧しい大学院生にとって満足できる対応ではない), かつ, 移行のための複雑な実行支援機構ランタイムサポートシステムではなく, 道具と環境のほんの少しの統合のみが必要たるべきだということである.

1979年春, ベル研究所計算機科学研究センターでのLANで接続されたコンピュータのネットワーク上でUNIXカーネルがどの程度の範囲まで分配されうるのかを明らかにする試みが始まってすぐに, いくつかの問題が生じた. ネットワーク通信を分析する方法と, プログラム内でカーネルの処理をモジュール化する方法が分からなかったのである. ビャーネは, それらの問題がまさに, かつて適切な道具・・・・・を使わずに取り組むような真似を二度としないと誓った難題であると気付き, すぐに適切な道具・・・・・の開発に取りかかった. 新しい道具を作るにあたって彼が注目したのが, プログラミング言語Cである. 1972年に開発されたCは当時既に普及していて実績があったため, それをベースとすることで技術分野のコミュニティに参加できるという利点があった. また, Cは基本的な機能を全て備えており, それでいて余計な機能が無いので動作が速かった. Cの持つ危険性はあまり問題ではないとみなされた.

1979年10月, 彼はCにSimulaの機能を付け加えるために, Simula風のコードをCに変換する前処理器プリプロセッサCpreを作った. 書いたコードを一度Cに変換してから動かすことで, 高速な動作を可能にしようと試みたのである. Cpreはその後洗練されてゆき, 1980年の3月には実際のプロジェクトで使われるまでになった. Cpreに通すコードの言語をC with Classesと呼んだ. 中断や再開が可能なコルーチンをサポートするタスクシステムは, C with Classesの有用性にとって必須であり, 後のC++において鍵となる最初のライブラリになった.

次の4月から10月までの間にC with Classesは「道具」というよりも「言語」として認識されるようになったが, まだモジュール性と並行処理の機能を備えたCの拡張としかみなされていなかった. しかし, Cに本来存在しない並行処理をサポートするライブラリを書くためにクラスの継承とメンバ関数を利用した際にある重大な決定がなされた. 各々が別のモデルの並行処理を必要としている数々のアプリケーションのために, 観点の違う複数の並行処理を表現可能にすること. この決断が下されたことでこの言語は, 特定の分野のアプリケーションのためだけの拡張機能を持つCの変種にはならず, プログラムを作るための一般的な機能を提供する汎用言語になった. また, C並の実行速度, コードのコンパクトさ, データのコンパクトさを実現することがC with Classesの明確な目的であったため, 実行時の余分処理ランタイムオーバーヘッドによるプログラムの3%もの効率低下が指摘されるとすぐに余分処理は削除され, クラスオブジェクトの管理ハウスキーピングデータ*2も置かれなかった. さらに, C with Classesが使用できる状況が制限されることを防ぐにはCでできることと同じことがC with Classesでもできる必要があったため, Cのビット操作などの極めて低レベルな操作や型システムを意図的に破壊する危険な機能をC with Classesから取り去ることはできなかったが, 他の機能を充実させることでそのような推奨されない機能に頼る必要性は排除された. C with Classesはプログラマーに特定のスタイルを要求することなく, むしろ効果が実証された様々なスタイルを奨励し, よくある罠や落とし穴に引っかかることが無くなるように設計された.

このような数々の工夫により, 1982年の間にC with Classesは小さな成功を収めた. ここにおいてビャーネはC with Classesの後を継ぐ言語を作ることにした. 新しい言語の名前は「new C」や「C84」などの紆余曲折を経て「C++」となった*3. これがC++の誕生である. 1985年, C++で書かれたC++からCへのトランスレータをCで書かれたC++インタプリタコンパイルしたものに通したものに自身をトランスレートさせたものをCのコンパイラに通すという手法を用いて, 最初のC++コンパイラCfrontが作られ, リリースされた. C++の標準規格としてThe Annotated C++ Reference Manual(注解C++リファレンスマニュアル)が出版される1989年までの間, CfrontそのものがC++の規格だった.

初期のC++で, Hello worldを表示するコードは次のようになる[3]:

#include <stream.h>
int main(){
    cout << "Hello, world!\n";
}

これをC++コンパイラがトランスレートし, Cのコンパイラコンパイルし, リンカがI/Oストリーム用のオブジェクトファイルとリンクし, Munchがパッチを当てて*4, 最終的に「Hello, world」と出力するプログラムが生成される.

その後ARMがC++の機能を明文化し, テンプレートの仕組みを大幅に改善したり, 膨れ上がるライブラリへの対策として名前空間を導入したりした. また, C++が爆発的に普及するとともに公式な標準化が求められるようになったためAT&Tベル研究所の協力とHewlett-Packard社の主導のもとに1989年から開かれたANSI X3J16 C++標準化委員会は, 1991年ISO C++標準化委員会と合わさってWG21としてC++の規格を作り上げていった. 言語仕様や機能, ライブラリをよりよくすると同時に, 言語の方言化や沈滞を防ぐことを目的とした尽力の結果, 1998年ついに最初の標準規格であるISO/IEC 14882:1998が制定された. その後見つかったバグは2003年の改訂版ISO/IEC 14882:2003で修正された. AT&T, Borland社, IBM社, Microsoft社, Texas Instruments社など多くの組織や人間がそれぞれ独自の信念のもとに関わって作ったこの規格の中には後に短所が発覚した例外などの機能もあったが, 後から大きく規格を作りかえて混乱をまねくような真似はできないため, C++の仕様は今後非常に複雑な進化をとげてゆくことになる.

テンプレートとSTLの進化

C++の標準ライブラリ(Standard Template Library, 通称STL)には可変長配列ベクタ, 連結リストリスト, 順序付集合セット, 連想配列マップなど数々のコンテナがある. ここで, 総称的ジェネリックプログラミングと呼ばれる, 「コンテナはどんな型も要素として扱うことができるべきだ」(即ち, 例えばvectorは整数の配列も浮動小数点数の配列も, さらには配列の配列も扱うことができるべきである)という理想を実現するために, C++における最大の闇と言われる, テンプレートというものが使われている. C++においてテンプレートは重要な位置を占めており, C++はテンプレートとともに進化してきた. テンプレートで書かれたSTLのおかげで平衡二分木などの高度なデータ構造を楽に使えるようになり, もっと言えば自分で実装する能のない未熟なプログラマも気安く取り扱えるようになった. もっとも, 理解もせずにSTLを適切に使い分けられるかどうかはまた別問題だが. ただ木の回転などの複雑な処理は理解できても実装するのが大変なことがあるため, それに時間を費やすよりも専門のプログラマにより確実に最適化して実装されているSTLを使うというのはよい判断である.

STLはメモリ確保と解放*5の処理を選り分けて「アロケータ」と呼ばれるクラスにやらせている. コンテナの核心となる要素追加や探索などの処理と違って, メモリ操作の仕方だけは型ごとに異なるためである. だから, コンテナのテンプレート引数は本来は要素の型TとそのアロケータAの2つなのだが, 例えばvector<int, allocator<int>>などと書くのが面倒なので, 定義の際にtemplate<class T, class A = allocator<T>>と既定することでallocator<int>を省略してvector<int>と書けるようにしている. このことはコンパイラが吐くエラーメッセージによく表れてくる.

1998年のC++98で標準ライブラリにSTLとして用意されたコンテナは, vector, list, dequeの配列コンテナ3種, map, multimap, set, multisetの連想コンテナ4種, queue, priority_queue, stackのコンテナアダプタ3種, そしてstringやvalarray, bitsetなどのコンテナ相当である. これらは整理のために全てstd名前空間の中に入っており, 外から呼び出すときは名前の前にstd::を付ける必要がある*6. C++の規格はそれぞれのコンテナについて「どの機能を持たなければいけないか」を規定したが, 「それらをどのようなアルゴリズムを用いて実装するか」についての規定は一切しなかったため, コンパイラによって差異がある可能性がある. vectorは可変長配列で, 各要素への直接参照ランダムアクセスが定数時間で可能である. 末尾に適度な空き領域を設けておくことで, 要素の末尾への追加プッシュバック末尾からの削除ポップバックもかなり高速に行ってくれる*7. 挿入についても他のコンテナを上回るほどの高速性を見せる[5]. 演算子ではなくat関数を使うことで範囲外アクセスも防いでくれる. また要素にbool型を指定したときだけは1要素あたり1ビットしか使わないように特殊化されている. listは双方向連結リストで, 各要素から「次の要素」と「前の要素」が参照できるようになっていて, 要素の挿入と削除が定数時間で可能である. dequeはvectorよりも定数倍遅いが, 末尾だけでなく先頭にも空き領域を持っていて先頭への追加プッシュフロントも定数時間で可能なので, ARC077-C「pushpush」などのごく稀に特殊な状況において効果を発揮するらしい. またvectorよりも中間挿入が定数倍速いという説もある[6]. set, multisetは順序付き集合で, 比較可能な型に対して「要素を追加する」操作と「値を指定して, それに等しい要素が存在するか調べる, あるいは個数を数える」操作がともに対数時間で可能な二分探索木である. 「赤黒木」と呼ばれる平衡二分探索木によって実装されることが多い. それぞれのノードに対して子への参照が高々2つ格納されており, 一方の子孫は全て親より小さく, もう一方の子孫は全て親より大きいため, 根から順に大小比較を繰り返せば高速に探索できるのである. 注:蟻本に載っている. しかし昇順配列を先頭から追加するなどの特殊なケースにおいて偏りが生じないように, 常に回転処理を行って平衡を保つ必要がある. 注:蟻本にすら載っていない. setとmultisetでは, 既に存在する値を追加したときの挙動が異なる. キー(要素)の型K, アロケータAの他に, キーを比較するbool型関数を()演算子bool operator()(const K &x, const K &y) constとして持つ関数オブジェクトCをテンプレート引数にとる. Cはデフォルトでヘッダfunctional内のless<K>となる. テンプレート引数が省略されたときは前から順に適用されるため省略しない引数よりも前の引数を省略することができないので, より省略されやすいアロケータAが最後に置かれてtemplate<class K, class C = less<K>, class A = allocator<K>>となる. map, multimapは順序付き連想配列で, set, multisetの要素をキーと値のペアとしたものである. キーを指定して要素の追加と検索が対数時間で可能である. 配列が大きすぎて持てない場合によく役立つ. multimapでは同じキーに複数の値が対応するため, 値を取り出すためにはpair<multimap<K, V, C, A>::iterator, multimap<K, V, C, A>::iterator>型の変数が必要となる. queueはdeque, stackはvectorを内部に保持していて, それぞれFIFO(First In, First Out), LIFO(Last In, First Out)による要素の追加・取り出し・削除のみが可能である. テンプレート引数にdeque<T>, vector<T>をデフォルトとする内部コンテナCを持つ. priority_queueは要素の追加と最優先要素の取り出しがともに対数時間で可能で, 通常ヒープを用いて実装される. 優先度は普通「より大きい」または「より小さい」で, テンプレート引数Cmpで指定する(ただし最も優先度が低いものから取り出される). ヒープは完全二分木で, 常に親より子がCmpたるように保たれるため, 根が最もCmpでない要素となる. 内部コンテナはvectorである. テンプレート引数は<T, C, Cmp>であるから, 優先度関数Cmpを指定するためにはコンテナCも指定しないといけない. このように仕様に多少の欠陥が見られるが, ダイクストラ法やプリム法などでよく使われ有用性は高いので重要である(ただしダイクストラ法やプリム法は1980年代に発明されたフィボナッチヒープによってさらなる高速化が可能となってしまった). stringは文字列で, C言語の文字列charの使いにくさを克服するために作られたものである. =演算子による代入や==演算子による比較が可能で, 他のコンテナの要素としても安全に使用できる. valarrayは配列で, 2つの配列の各要素を加減乗除したり, 要素の総和, 最大値, 最小値を求めたり, いくつかおきに要素を抽出したりする処理が最適化されている. bitsetは固定長ビット配列で, 複数のビットをまとめて論理演算やシフトが効率的に行える. 2n通りの組み合わせが考えられるビット探索などで動的に使用すると絶大な効果を発揮する.

これらのSTLには, 要素を参照する手段としてポインタの代わりにイテレータというものが用意されている. 先頭のイテレータはbeginメンバ関数, 末尾のイテレータはendメンバ関数で得られ(ただしend関数が返すのは末尾の次の要素へのイテレータ), 単方向イテレータは++演算子によるインクリメント, 双方向イテレータは加えて--演算子によるデクリメント, さらにランダムアクセスイテレータは2項+演算子, 2項-演算子による移動や[]演算子によるアクセスも可能となる. コンテナを先頭から末尾まで走査するときなどによく役立つが, イテレータで指定した要素を削除するときなどはコンテナによって挙動が異なるため注意が必要である.

STLの他には安全なスマートポインタ*8auto_ptrを提供するmemory, ソートや二分探索をするalgorithm, 三角関数指数対数累乗平方根絶対値床天井などを算出するcmath, 国による環境の違いに対応するためのlocaleなどが標準ライブラリに用意された.

C++98が発行されたのち, 当時のC++の標準ライブラリに足りなかった機能を補うために, 有志プログラマのボランティアによってオープンソースのライブラリであるBoost C++ Librariesが作られた. 汎用整数を扱うBoost.Integerを皮切りに, 分数を扱うBoost.Rationalや関数オブジェクトを扱うBoost.Functionalなど次々と有用なライブラリが実装されていった. 開発者の中にはC++委員会のメンバも多くいた. さて, WG21は東芝, 日立, 富士通, NECを含む日本企業の合弁組織に促されてthe Performance Technical Report(TR)をもとにC++に足りないものをかき集め, C++を大きく進化させるために動き始めた. ビャーネ他委員会のメンバは, 規格というものは多くの人に広まらないと欠点が見つからないため, 一定の期間が経過するまで新機能の開発を開始してはならないという規則がISOに存在すると思って2002年までC++の新機能の作業を始めなかったのだが, 実はそんな規則は存在しなかった. このせいでC++の進歩は大きく遅れた. 新たなC++には2つの目標が設定された: C++を, (数値計算Windowsアプリケーションに特化した機能でなく)システムプログラミングとライブラリ構築に適したよりよい言語にすること, そして, より画一的かつ確実で初心者にとっても使いやすく, 教育に適したものにすることである. 新しい規格はC++0xと呼ばれ, 公開時にxに年数を入れる予定だったが, 結局間に合わずにC++11(ISO/IEC 14882:2011)となった. C++11では標準ライブラリが強化されただけでなく並行処理が可能になったが, GUIやインターネットに関するライブラリはTR2に回されるなどして実装されなかった.

また, テンプレートはそもそもSTLを書く手段として作られたものであるが, そのために再帰テンプレートや特殊化による分岐処理をできるようにした結果, C++のテンプレートがチューリング完全*9になっていることが2003年明らかになった[7]. つまりコンパイラが吐く実行ファイルではなくコンパイラそのものに任意の処理をさせられるのである. さらに展開に失敗してもめげずに他の可能性を試すSFINAE(Substitution Failure Is Not An Error, 置き換え失敗はエラーではない)を利用することによりコンセプトを指定したテンプレートメタプログラミングの可能性がさらに広まった. C++ユーザーが新たなコーディング技術を次々と発見するため, 委員会はC++をそれらに適した形に変えていかなければならなかった. C++11ではauto型推論, decltype型取得, usingエイリアスが追加された.

C++11を作る際に大きな弊害となったのが, 互換性であった. 2004年時点で3,270,000人がC++を使っていたため[8], C++の悪い機能を勝手に削除して古いC++で書かれたコードが動作しなくなってしまうと, 甚大な被害が出る恐れがある. 利用者の中にはMicrosoft社やApple社の人間も多くいた. もういっそ全てのファイルに「このコードはC++○○で書かれています」とでも書かせておけばよかったのだが, 残念ながら20世紀にそんな発想は無かった. 幸いにして, 賢いプログラマたちは悪い機能を使うことを自ら避けていたため, 「この機能は非推奨で, いずれ削除されます」と発表することで解決を図ることができた.

C++11ではauto, 拡張for, ラムダ式などの言語仕様が追加され, 冗長なコードが非常に簡潔に書けるようになった. 例えばそれまでfor(std::vector<int>::iterator it = v.begin(); it != v.end(); it++) std::cout << *it << std::endl;と書いていたものはfor(auto i:v) std::cout << i << std::endl;となる. constexprによるコンパイル時定数計算も可能になり, プログラマの手間がさらに省けた. また, 標準ライブラリにたくさんの機能が追加された. 並行処理に関する機能はthread, mutexの他に高レベルなfuture, promiseから低レベルなatomicまで幅広くサポートされ, 安全な資源管理ポインタunique_ptr(コピー不可), shared_ptr(コピー可, 遅い), weak_ptr*10, タプルtuple(3つ以上の変数の組), 正規表現regex(文字列をより解析しやすくなる), 乱数を扱うrandom_device(処理系ごとに非決定論的で予測不可能な乱数を生成する)などの新たな機能も追加された. また, テンプレートの山括弧を2つ続けて閉じるときにスペースを開ける必要を無くすというちょっとした配慮もなされた. また, オブジェクトのアドレスを取得する手段としてはもともと&演算子が用意されていたが, &演算子の中身を自由に再定義オーバーロードして改変できるようになったため, 絶対に正しくアドレスを取得するための新たな手段としてaddressof関数が用意された. cmathヘッダの数学関数には逆双曲線関数, 2を底とする指数対数関数, 指数が0に近いときの指数対数関数を高精度に計算する関数, 立方根をとる関数, 2つの実数の平方和の平方根(言い換えると直角三角形の斜辺や2点間の距離, ベクトルの大きさ)を算出する関数, 誤差関数と相補誤差関数, ガンマ関数(階乗)とその自然対数, ゼロ方向への丸め, 四捨五入, そして丸め方を指定できる丸め関数, 小数の余りあり割り算, 2つの実数を与えると絶対値を変えずに一方の符号を他方に合わせる符号関数, NaNを生成する関数, 実数をできるだけ小さく増やす, または減らす関数, 2つの実数の最大値, 最小値, 差を求める関数, 掛け算と足し算を同時に行う(3つの実数x, y, zを与えるとx*y+zを計算する)関数, 数の属性(有限か無限かなど)を判定する関数, 大小比較関数が追加された.

そしてSTLには固定長配列array, 単方向連結リストforward\_list, 非順序連想コンテナunordered_map, unordered_multimap, unordered_set, unordered_multisetが追加された. arrayはコンパイル時に大きさが決定するためC配列に近い. forward_listはlistと違って次の要素は参照できるが前の要素は参照できない. unordered_が付いた連想コンテナは二分木を使わず, キーをハッシュ関数で小さな整数に変換して要素への参照番号とするため, 要素をイテレータで順番に取り出すことはできない.

Boostの方では, 2011年に計算幾何を扱うBoost.Geometry, 2013年に多倍長整数, 多倍長浮動小数点数(理論上無限の精度をもつ)を扱うBoost.Multiprecisionが追加された. 標準委員会が2014年に制定したC++14(ISO/IEC 14882:2014)ではunique_ptrを生成するmake_unique関数*11が追加され, autoやconstexprが拡張された. C++14を策定する際に組織されたStudy Group(SG)の成果はC++14では追加されなかったが, C++17の革新的な機能に貢献した. 2017年にはC++17(ISO/IEC 14882:2017)が策定され, 構造化束縛ストラクチャードバインディングによりついに関数が複数の値を返すことができるようになった. また長年プログラマ, 特にコードゴルファーを苦しめてきた副作用完了点問題も, ついに評価順序が規格上厳密に定められ処理系依存でなくなることにより解消した. 「Clangを信じろ」の時代は終わり, 「ちょっとかっこよくショートコーディングしてみる」というプログラマにとってのささやかな楽しみが許される時代が来たのである. また任意の型を代入できるanyクラス(もとはBoostにあった)の登場によりRubyのような全てのオブジェクトの元となるスーパークラスが疑似的に再現できるようになった. cmathヘッダの数学関数にはベータ関数, 指数積分, エルミート多項式, ラゲール多項式, ラゲール陪多項式, ルジャンドル多項式, ルジャンドル多項式, 球面調和関数のθ成分, 第一種完全楕円積分, 第二種完全楕円積分, 第三種完全楕円積分, 第一種不完全楕円積分, 第二種不完全楕円積分, 第三種不完全楕円積分, ノイマン関数, 球ノイマン関数, 第一種ベッセル関数, 第一種変形ベッセル関数, 第一種球ベッセル関数, 第二種変形ベッセル関数, そしてリーマンのゼータ関数が追加された. numericヘッダには最大公約数と最小公倍数も追加され, これらをプログラマが自分で実装する必要は無くなった.

*1:Wikipediaにはビャーネ・ストロヴストルップと記載されており, 実際の発音もストロヴストルップに近いものであるが, 綴りがBjarne Stroustrupなので英語圏ではストラウストラップと呼ばれることが多いようである.

*2:オブジェクトのメンバをメモリ上のどこに配置するかを記憶する管理データを新たに設けると余分なメモリ領域を消費して処理時間も遅くなるが, メンバをそれぞれ独立に扱えばそのコストは無くなる.

*3:「++」はCにおいてインクリメント, 即ち「1つ先へ進む」ことを意味する. ただし後置++演算子の使用は前置不可能な場合を除きC++で推奨されていない.

*4:コンパイルした複数のファイルをリンクするだけではグローバルオブジェクトのコンストラクタやデストラクタを呼び出すことができないので, 実行ファイルにパッチを当てる作業が最後に必要となる.

*5:ときどき「解放」ではなく「開放」と書かれていることがあるが, C++に限らず多くの規格書などによれば「開放」ではなく「解放」が正しいとされている.

*6:using namespace std;という「魔法の呪文」を書いておけばいちいちstd::を付ける必要はなくなる.

*7:g++では新たにメモリを確保するときにもとの2倍の領域をとるが, メモリ効率を考えると黄金比より少し小さい1.5倍が適切だという説もある[4]. vectorには一応空き領域のキャパシティを調整する機能もついているが, そんなことをしたがる人がもしいるとしたら自前で配列クラスを作っているだろう.

*8:C言語のポインタはコンピュータのメモリを直接書き換えることができる. いくら熟練したプログラマでも複雑なプログラム内で間違った場所のメモリを書き換えてしまうことがあり, 昔はそれがシステムの破壊につながることも多かった. 最近のOSではプログラムがクラッシュする程度で済むが, ポインタが危険だという考えは根強く残っている. 安全を確保するためにコンパイルにかかる時間が長くなったりプログラム自体が少し遅くなったりするのは, プログラマに快適なコーディング環境を提供するための小さな犠牲である.

*9:コンピュータを一般化した仮想の機械チューリングマシンと同じことができるということ. つまり現代のコンピュータを使って計算可能な処理ならば, 理論上全て可能である.

*10:これら3つが追加されたことによりauto_ptrは非推奨とされた

*11:委員会メンバーのブログ[9]によるとC++11では実装し忘れていたらしい.

数学的でない命題に自分を食わせる

「どんな法則にも例外がある」という命題があります。

この命題について考えるためには,まず「法則」とは何なのかはっきりとさせなければなりません。

例えばそうですね…英語における文法規則としてみましょうか。そして次のように捉えます:

 

規則1:肯定文は主語+動詞の順番。(例外:Never have I seen such a beautiful sunset.)

規則2:不完全自動詞の場合動詞の後に補語が来る。(例外:The cat is in the kitchen.)

規則xxx:任意の規則 n (1≦n≦xxx) について,これを満たさないような場合が存在する。

 

そして今後 xxx = 0 とし,規則0即ち「例外の法則」について,規則0自体がこれを満たすのかどうかを考えます。

 

次の2つの場合が考えられます。

(1)規則0を満たさないような場合は存在しない。

(2)規則0を満たさないような場合が存在する。

 

(1)の場合,規則0を満たさない例として,規則0そのものが挙げられるので矛盾,よって規則0は偽です。

ならば(2)は真か?規則0の存在によって命題の排中律が脅かされていることに注意してください。

(2)の場合,さらに2つの場合が考えられます。

 

(2.1)任意の規則 n (1≦n<xxx) が規則0を満たす。

(2.2)ある規則 n (1≦n<xxx) は規則0を満たさない。

 

(2.1)任意の規則 n が規則0を満たすならば,規則0自体が規則0を満たさないので矛盾します。

(2.2)規則0を満たさないような規則 n が存在するならば規則 n や規則0が規則0を満たさないので,規則0は真です。

 

よって矛盾の発生しない(2.2)のみが真実となります。

 

これを日本語的に言うと,例外の法則自体にも例外があるという結論が最も正しいということになります。

 

もう一つ,数学的でない命題を考えてみます。

「何事もほどほどに」という命題です。

例えば身長は高ければ高いほど良いわけではなく,(様々な条件を揃えてたくさんのパラメータをスカラー値に写像して考えれば)最適な身長 x0 cmが定まります。

さて,ここでまた新しい命題が生まれました。「身長は x0 cmに近ければ近いほど良い」です。これに対して「何事もほどほどに」を適用するとどうなるでしょうか。

「x0 cmに程よく近いのが良い」つまり「ある身長x1 cmとx2 cm (x1, x2は x0 に程よく近い)が存在して,x1 cmまたはx2 cmに近ければ近いほど良い」です。もう分かりましたね。このやり方を再帰的に繰り返すことによって,どんな身長についても,「それは最適ではない」ということになってしまいます。

この問題に対しては面白い解決策があります。

「x cmに近いほど良い」に「何事もほどほどに」を適用して「x cmに程よく近いのが良い」とする操作を何度も行おうとしているわけですが,この「何度も行う」というところに対して「何事もほどほどに」を適用すると,この操作はどこかでストップさせなければなりません。ほどほどなところで,です。

よって最適な身長がすっきりと求められることになります。

 

やってみたい人はこの理論を反駁してみてください。

 

以上です。読んで下さってありがとうございました。