[ アカウントをゲット! ]
此處、Bjarne Stroustrup博士に関する話題を取上げています。それは私個人が博士の大ファンであることも関係しますが、それ以上に私達の世代(年齢がばれるのではっきりとは書きませんが)にとって始めてのOO言語と言えばC++が最初なのです。今から考えればそれ以前にもOO言語と呼ばれる言語はあることはあるのですが、実用に耐えうるOO言語としてはC++が始めてではないかと私は思います。
そのStroustrup博士が新年早々発表したエッセイが"What Should We Teach New Software Developers? Why?"です。このエッセイに述べられている博士の危機感は、最新著"Programming: Principles and Practice Using C++"の第1章"Computers, People, and Programming"の中でも述べています。と言うか、こういう危機感が博士にこの本を書かせた最大の要因なんだと思います。
兎も角も最新エッセイの私訳を以下に載せておきます。
新人開発者に何を教えるべきか? また何故か?
2010年1月 Bjarne Stroustrup
コンピュータサイエンスはソフトウェアシステム開発の中心であるはずだ。そうでなければ、私達は個人の経験と経験則に頼らざるを得ず、開発と保守に余分な高いコストを払う、有能性が劣り信頼度が足りないシステムとなる。産業上の業務の改善を考慮に入れた教育の変革が必要である。
問題
多くの処で、コンピュータサイエンス教育と産業界が必要とすることに乖離がある。次のようなやり取りを考えよう。
有名なコンピュータサイエンス教授(誇らしげに):"私達はプログラミングを教えていない。コンピュータサイエンスを教えている"
産業界経営者:"彼等はごく簡単なことしかプログラム出来ない"
多くの場合、どちらも正しいし、上っ面なレベルではない。単にありふれたプログラマを教えることが学界の仕事ではなく、産業界のニーズは単に"多才で高レベルな思索家"と"科学者"ではない。
別のコンピュータサイエンス教授:"私はコードしない"
別の産業界経営者:"私達はコンピュータサイエンス卒業生を雇わない。コンピュータサイエンス卒業生に物理学を教えるよりも、物理学者にプログラムすることを教える方が簡単だ"
どちらも的を得ているが、理想の世界ではどちらも根本的に間違っている。練習しない(多くの場合練習したことがない)ことは教えられないし、従って理解しないという点では、その教授は間違っている。一方、ソフトウェア品質に必要なものが馬鹿みたいに低く設定されていて、物理学者(コンピュータサイエンスの訓練を受けていない他者でも)がうまく対処出来る時のみ、産業界経営者は正しい。明らかに私は、コンピュータサイエンスもマスターするために非常な努力を払ったことがある物理学者達のことを言っていない。そのようなスキルの組合せは私の理想にある。
コンピュータサイエンス教授(一人の学生について):"彼は産業界に就職した"
別のコンピュータサイエンス教授:"残念。彼は非常に有望だった"
この乖離は多くの問題の根本であり、事態の改善を複雑にしている。
産業界はコンピュータサイエンス卒業生達にソフトウェアを構築(少なくとも彼等の経歴のうちで当初は)してほしい。そのソフトウェアはしばしば、長く生きたコードベースの一部であり、高い信頼性が必須の組込み又は配布されるシステムのために使用される。しかし、多くの卒業生が実質、趣味人の活動の他に、ソフトウェア開発における教育又は訓練を持っていない。特に、大部分がプログラミングを宿題を完成するための最小の労力と見ており、系統だったテスティング、メンテナンス、ドキュメンテーション、他の人によるコードの使用を含む広範囲な見解を滅多に持たない。また、多くの学生が一つのクラスで学ぶことと他で学ぶことを結び付けることに失敗している。だから、アルゴリズム、データ構造、ソフトウェアエンジニアリングで高い成績を持つが、オペレーティングシステムのクラスではデータ構造、アルゴリズム、ソフトウェアの構造を全く無視し、それでもソリューションをハックする学生を私達はよく見る。その結果は、下手に動く保守不能な滅茶苦茶な状態だ。
多くの人にとって、"プログラミング"は破廉恥なハッキングと他の人のライブラリ(何かが行われているという漠然とした考えだけで)を呼ぶことの奇妙な組合せになってしまっている。"メンテナンス"及び"コードクオリティ"の概念は概して忘れられるか又は、下手に理解されている。産業界では、"システム"を理解し"ソフトウェア設計出来る"卒業生を探す困難についての苦情は普通であり、現実を反映している。
だが、私のコンピュータは最近クラッシュしない
ソフトウェアについて苦情を述べることは流行りの気晴らしだが、多くのソフトウェアがここ10年に渡って良くなっている(正確には、その前の10年に改良したので)。残念ながら、その改良は人間の労力とコンピュータリソースの点からすさまじいコストを払って来ている。基本的に私達は、ランタイムチェックと巨大なテスティングの無限の層を追加することにより信頼出来ない部分からまずまずな程度に信頼出来るシステムを構築する方法を学んで来た。コードそのものは時々変わって来ているが、いつも良い方のためとは限らない。しばしば、ソフトウェアの多くの層と設計の中の入組んだ共通する従属物は、個人(だが、有能な)が十分にシステムを理解するのを妨げる。これは未来にとって凶兆である。私達は分からないし、システムのクリティカルな面を評価さえ出来ない。
膨れて不透明なシステムを構築する圧力に抵抗するシステム構築者は勿論いる。コンピュータ化された飛行機がクラッシュしない、携帯電話が動く、メールが時間通りに届く時、私達は彼等に感謝出来る。ソフトウェア開発を分別が備わり且つ信頼に足る原理、ツール、手法の集まりにしている努力に対して、彼等は賞賛に値する。残念ながら、彼等は少数であり、膨れ上がったソフトウェアが人々の印象と考えを支配している。
同様に、理論と産業上の業務の乖離と格闘している教育者がいる。彼等も賞賛と積極的な支援に値する。もっとはっきり言えば、私が知っている教育機関はすべて実践的経験を与える目的のプログラムを持ち、一部の教授は特別なプログラムを成功させることに人生を賭けている。しかし、大きな視点で見るならば、私は心が動かない。すなわち、幾つかのプロジェクト又は実習はいいスタートだが、バランス良いカリキュラムへの包括的アプローチの代替にはならない。その名称を、"コンピュータサイエンス"を超えて"ソフトウェアエンジニアリング"又は"IT"と呼ぶことは大局的見解の違いを意味するのかも知れないが、問題は新しい状況への移動の後に少し違った外観で再出現する意地悪さを持っている。
私の"産業界"と"学界"の特徴的記述は風刺漫画とぎりぎりだが、少し経験のある誰もが特徴付けの中に現実の反映の一部を認識するだろうと私は確信している。私の大局的見解は産業界の研究者と管理者(AT&Tベル研究所での24年間、そこでの7年間の部長)のそれであるが、今6年間は学界(エンジニアリング大学のコンピュータサイエンス部門で)だ。私は多く旅をし、毎年数多くの企業(大部分が米国)からの技術的及び経営的な人々と真剣な議論を重ねている。私は、大学が生み出していることと産業界が必要としていることの食違いを、コンピュータサイエンスの生存能力とコンピュータ産業界両方への脅威として見なしている。
批判的にコンピューティングに頼っている多くの組織は危険な程に技術的スキルが低くなっている
学界と産業界のギャップ
だから私達は何を出来るか? 産業界は最近のツールと技術で十分に訓練された"開発者"を雇いたい、一方学界の大望は多くの良い教授を輩出することである。進歩するためには、これらの理想はうまく連携しなければならない。産業界に行く卒業生はソフトウェア開発を良く理解していなければならないし、産業界は新しい考え、ツール、技術を吸収するためのずっと良いメカニズムを開発しなければならない。中途半端なプログラマが害なことをするのを妨げるために考案された文化の中へ良い開発者を入れることは、新開発者が新しい良い事を何をするのでもずいぶんと制約を受けるので無意味である。
スケールの問題を指摘させて欲しい。多くの産業システムは何百万行のコードから成る一方で、今まで1000行より多くのプログラムを書かないでコンピュータサイエンスのトッププログラムからの優等成績で学生は卒業出来るのだ。産業プロジェクトの全てが多くの人々を巻き込む一方で、コンピュータサイエンスのプログラムはチームワークの意欲を削ぐ意味で個人の働きに重きを置く。これを認識しているので、多くの組織が開発者のスキルへの信頼を最小にするため、ツール、テクニック、言語、運用プロシージャの簡明化に焦点を当てている。すべての人を最小公分母に成下げるのだから、これは人的才能の浪費である。
産業界は信頼のおける真実のツールとテクニックに頼りたいのだが、"銀の弾丸"、"変形させるブレークスルー"、"キラーアプリ"やその他の夢にも耽っている。彼等は、コードクオリティの面倒を見るには余りにも優秀な少数の"御意見番"に指導される最小のスキルを持つ、交換可能な開発者で以て運営出来るようにしたい。これは、基礎的ツール(プログラミング言語やオペレーティングシステムのような)の選択と、モノカルチャー(訓練と配置コストを最小にするため)に対する願望において、恐ろしい程の保守主義へと繋がる。結局、これは壮大なプロプライエタリと相互非互換なインフラストラクチャとなる。開発者がアプリケーションを作ることを可能にするためには基礎ツールを超えるものが必要であるし、プラットフォーム提供者は基礎ツールの共通性にもかかわらず開発者を閉じ込めるものが欲しい。報酬システムは壮大な企業スキームと短期間成果の両方に偏る。成果のコストは新しいプロジェクトの失敗の割合でもあるので人を怯ませる。
産業上の現実とその他類似の妨害に直面して、学界は最善を尽くしながらも内向きになっている。同好の人の小さなグループによって孤立して入念に研究される現象、確かな理論的基礎の構築、理想化された問題のための完全な設計と技法を作ること。時代遅れのスタイルで書かれた膨大なコードベースを扱うプロプライエタリなツールはこのモデルに合わない。産業界と同様に、学界もマッチすべき報酬構成を開発している。これ全体は、うまく記述された学界テーマの内で重工業的コースの着実な改良において完璧に合っている。従って、学界の成功は不適任者の選別のような産業界のニーズに合っており、産業界は特殊化されたインフラストラクチャの開発コストと同様にトレーニングコストを背負う必要がある。
"産業界が開発者にそれなりの報酬を払えば、問題無いだろう"と一部の人は何時も言う。それは助けになるかも知れないが、同じ種類の仕事に対する報酬には役に立たない。実現可能な代替のためには、産業界は良い開発者を必要とする。中途半端なスキルを持つ交換可能な人が配置された集積ラインとしてのソフトウェア開発の考え方は根本的に欠陥があり無駄である。その分野から優秀な人を追い出し、学生にその中に入る意欲を削ぐ。この残酷な堂々巡りを壊すためには、学界は関連スキルを持つ学生をもっと育てなければならないし、産業界はそれらのスキルを当り前にするためにツール、テクニック、プロセスを採用しなければならない。
プロフェッショナリズムの夢
"コンピュータサイエンス"は実に酷く、誤解させる言葉だ。コンピュータサイエンスは何よりもコンピュータについてではなく、何よりもサイエンスではない。むしろ、コンピュータの使用方法と計算("アルゴリズムと計算量の考え")を伴なう作業と考え方の方法についてである。しばしばコンピュータを使用して、サイエンス、数学、エンジニアリング的側面を関連付けるのである。コンピュータサイエンスの大部分の人にとって応用分野の一つだが、応用から離れた"純粋コンピュータサイエンス"は典型的に不毛だ。
アプリケーションを構築するコンピュータサイエンスの人とある他の分野(医学又は物理学)のプロフェッショナルは何が違うのだろうか? その答えは"コンピュータサイエンスのコアの熟達"のはずだ。その"コア"は"何であるべきか"? 確立されたコンピュータサイエンスのカリキュラム、具体的にはアルゴリズム、データ構造、マシンアーキテクチャ、信念に基づいたプログラミング、幾らかの数学(何よりも証明ベースと計算量推論を教えること)、システム(オペレーティングシステムとデータベースのような)の殆どを含むだろう。その知識を集積することと大きな問題を扱う方法の考え方を得るためには、全ての学生が多くのグループプロジェクト(ソフトウェアエンジニアリングの基礎と呼べるだろう)を完了しなければならない。理論と実際の間にバランスがあることが本質的だ。コンピュータサイエンスはただ原理理論ではなく、ただコードをハッキングすることではない。
このコアは明らかに、全体としてコンピューティング分野よりも遥かに"コンピュータ指向"だ。従って、コンピュータサイエンスの中で特殊化(例えばグラフィックス、ネットワーキング、ソフトウェアアーキテクチャ、ヒューマンマシンインタラクション、セキュリティ)の追加無くして、誰もコンピュータ科学者と呼ばれないはずである。しかし、それでもまだ不十分だ。コンピュータサイエンスの実際は本質的に応用であり、学際的であるので、全てのコンピュータサイエンスプロフェッショナルは他のある分野(例えば、物理学、メディカルエンジニアリング、歴史、会計学、フランス文学)のちょっとしたものと同等を持つべきである。
経験ある教育者は"だが、これは不可能だ! 学生の誰もその全てを4年間でマスター出来るはずがない"と評するだろう。それらの教育者は正しい。つまり、何かを与えなくてはならない。私の提案は、コンピュータ科学者として実践すべき最初の課程の資格は修士(全体として修士用に考えた)であり、追加される最終年又は2年のある学士課程ではない。研究する計画を持つ人は通常博士号を目標にするだろう。
多くの教授は"私にはプログラムする時間は無い!"と反対するだろう。しかし、ソフトウェアプロフェッショナルになりたい学生を教える教授は時間を作り、組織はプログラミングのために彼等を報いる手段を見つけなければならないだろう。コンピュータサイエンスの究極の目標は良いシステムの生産を助けることだ。何年も外科手術を教えることに苦痛でないような人を信頼するだろうか? 鍵盤を触らないピアノ教師をどう思う? コンピュータサイエンス教育は学生を、完全なシステム内のアプリケーションの熟達とコードの美しさの鑑賞のために学ぶ教科書を超えた処へ導かなければならない。
私は"プロフェッショナル"という言葉を使う。多くの意味と含意がある言葉だ。医学やエンジニアリングの分野では、免許を意味する。免許は非常にトリッキーで感情的なトピックだ。しかし、私達の文明はソフトウェアに依存している。個人的嗜好と企業原理に基づいてコードのクリティカル部分を本質的に誰かが変更出来ることは道理のかなったことなのか? そうならば、50年間は道理のかなったことなのか? 何百万の人が依存する多くのソフトウェアが保証なしに来ていることは道理のかなったことなのか? 実際の問題は、免許を通して強要されるプロフェッショナリズムは知識、ツール、テクニックの大きな共有体を持つことに依存していることなのだ。免許を持つエンジニアはビルが認められた技法と材料を使って構築されていることを証明出来る。(私が早めに言った通り)コンピュータサイエンス能力の広く受入れられるアウトラインの欠如において、ソフトウェアアプリケーションのためにはそれをどうやってするか私は分からない。今日、資格試験(又はより現実的には、医学委員会のような、様々なサブスペシャリストの試験の集まり)を考案する人々の集団の選び方さえ私は知らない。
産業界はそのギャップを埋めるため何を出来るだろうか? 学界について語ることよりも"産業"と"産業ニーズ"を特徴付ける方がずっと困難だ。何と言っても、学界は、その目標を達成するために公平に標準構成と標準アプローチを持つ。産業界は遥かに様々異なる。システム構築その他のアプローチには、大小、営利又は非営利、最先端又は並みがある。従って、私は治療法の処方を始めることすら出来ないでいる。しかし、学界産業界のギャップに直接関係した意見を持っている。つまり、批判的にコンピューティングに頼っている多くの組織は危険な程に技術的スキルが低くなっている。
産業界経営者:"技術的専門家の内部調達は生存のためには危険である"
組織の記憶と新しい才能をリクルート及び発掘するためのインフラストラクチャ無くして、どの組織も成功するはずがない。ソフトウェア開発に興味のある大学人とコラボレーションを増やすことは両方に実りが多いだろう。共同研究と、単なるトレーニングコースを超える生涯学習の重視はこの点で重要な役割をするであろう。
結び
私達はもっと良くしなければならぬ。私達がするまでに、インフラストラクチャは軋み、膨れ、リソースを吸収し続けるだろう。結果的に、一部分は予測不能で悲惨な方法で(インターネットラウティング、オンラインバンキング、電子投票、電力網コントロールを考えてみよ)壊れるかも知れない。特に、学界産業界のギャップを両側で変革することにより縮小しなければならない。私の提案は、コアプラス、特殊化とアプリケーション分野に基づいてコンピュータサイエンス教育の構成を定義し、最終的にソフトウェア加工品とそれらを生産する、少なくともコンピュータサイエンスのプロフェッショナルの幾人かの免許を目指すことである。これは技術的専門家のための産業界学界参加の生涯教育の重視と相携えて進むかも知れない。
Bjarne Stroustrup博士の最新著書"Programming: Principles and Practice Using C++"の最終章第27章は"The C Programming Language"なのですが、Dennis M. Ritchie博士の言葉"C is a strongly typed, weakly checked, programmnig language"を冒頭で引用しております。C言語の生みの親が、正にそのものズバリなことを言っているのが可笑しいのではなく、これがどこからの引用なのか私には分かりませんでした。と言うのはずっと昔にもRitchie博士がこの言葉を言っているのを見た憶えがあって、あれこれ探してみたのですが原典を見つけられませんでした。Stroustrup博士が直接Ritchie博士から聞いた言葉なのか、又はK&Rの初版本で書かれていた言葉なのか(第2版には、それと良く似た言葉がありましたが、そのものずばりではないようです)も知れません。いずれにせよ、ご存知の方、ご教示下されば有難いです。
その原典探しの過程において、Ritchie博士の"The Development of the C Language"にも当然目を通しました。この文献は1993年に書かれたもので、月日の経つ早さに感慨深いものがあり、私も最近は歳を喰ったのか、昔の文献を読むと何か感傷めいた気持ちになり、昔の人は本当に偉かったんだなと思います。Stroustrup博士がC++を作るにあたって、1980年代初頭のベル研でRitchie博士らと机を並べながら、Cを最大限に継承しようと考えたのは当然でしょう。C言語コンパイラの「弱いチェック」という弱点も、1970年代初頭のマシンのメモリがわずか48Kバイトしかない時代でしたので、それはそれで意味があったのだとStroustrup博士は擁護しています。そういうことも"Programming: Principles and Practice Using C++"の第22章"Ideals and History"に述べられており、私はこの本を多くの人に勧めるのはただの技術書ではなく、こういう歴史も取上げられていて読んで非常に面白いからです。この第22章には、あのNiklaus Wirth博士も登場しており、当然Pascalも取上げているのですが、私が物足りないなと思ったのは、Stroustrup博士はPascalについては(意外にも?)簡単に触れているだけなんです。例えばStroustrup博士のD&Eなんかを読むと辛辣な評価を下しているのですが、"Programming: Principles and Practice Using C++"はやはり教育書の意味合いもあって、番外編みたいな事項は無駄だということでしょう。
Pascalと言えば、Linus Torvalds氏のPascal嫌いも有名ですが、もっと昔にPascalを酷評した人がいます。御存知、K&Rの共著者でもあるBrian W. Kernighan博士ですが、博士は1981年に"Why Pascal is Not My Favorite Programming Language"という論文を書きました。この有名な文献がどうも和訳がまだ無い(これには驚きました)ようでしたので、私訳を以下に載せておきます。
尚、脚注、謝辞、文献表等は煩雑なので、本質的に関係の無い事項は省略しております。
追記: 1月29日
この私訳を掲載してから、「Ada,C,Pascal―言語の比較と評価」と言う本に、Kernighan博士のこの論文の翻訳があると知りました。何と1986年6月のものです。
自分の不明をお詫びします。ですが、この私訳を取り下げる必要があるとは現在のところ思われませんので、このままにしておきます。
Pascalが私の好きな言語でない理由
1981年4月2日 Brian W. Kernighan
要約
プログラミング言語Pascalは、コンピュータサイエンス教育における授業の主要な言語となっている。また、その後の言語開発に強い影響を与えている(特に、Ada)。
Pascalは元々主として教育用言語として意図されたが、シリアスなプログラミング(例えば、システムプログラミング作業やオペレーティングシステムさえも)のためにも益々推奨されている。
Pascalは、少なくともその標準の形は平凡であり、シリアスなプログラミングに向いていない。この論文は、その理由の幾つかの私個人の発見を議論する。
1. 起源
この論文は2つのイベントに起源を持つ。すなわち、CとPascalの比較についての論文(1, 2, 3, 4)が次々に発表されていることと、'Software Tools'(5)をPascalで書き直す私個人の試みだ。
CとPascalの比較は、殆どLearjet(訳注:ビジネスジェット機)とPiper Cub(訳注:軽量プロペラ機)の比較に似ている。一方は何かをさせるため、他方は学習のため。従って、そのような比較は幾分非現実な趣がある。だが、'Software Tools'の改訂版は更に的を得た比較だと思われる。その中で、プログラムは元々Ratfor(プリプロセッサで実装された、Fortranの「構造的」方言)で書かれた。Ratforは実は変装したFortranだから、Pascalがもたらす重要なもの、つまり文字処理にもっと適したデータタイプ、一般の人のデータ編成をうまく定義するためのデータ構造化能力、データについて真実を語る強い型付け、を持っていない。
Pascalでプログラムを書き直すつもりよりも、もっと困難であることが判明した。この論文は、プログラミングのためのPascalの適合(プログラミングのための学習と区別して)について経験した教訓を引出す試みである。それは、Pascalと、C又はRatforの比較ではない。
プログラムは最初、バークレイのカリフォルニア大学で開発されたPascalインタープリタpiによりサポートされたPascal方言で書かれた。その言語は、JensenとWirthの名目上の標準(6)に近く、良い原因診断と注意深いランタイムチェックが付いている。それ以降、プログラムは4つの他のシステムでも走り続け、プリミティブな新しいライブラリを除いて変更は無い。4つの他のシステムとは、アムステルダム自由大学(以下、Vrije Universiteitを意味する、VUとして引用する)からのインタープリタ、バークレイシステムのVAX版(本当のコンパイラ)、Whitesmiths, Ltd.から提供されているコンパイラ、Z80上のUCSD Pascalである。最後を除いて、これらのPascalシステムはすべてCで書かれている。
Pascalは議論の多い言語である。最近の参考文献表(7)は"議論、解析、討論"の表題の許に175項目をリストしている。最も屡々引用される論文(非常に読む価値がある)は、Habermann(8)による強い批判、LecarmeとDesjardinss(9) による同じく強い返答だ。BoomとDeJong(10)による論文も良い読み物だ。Wirth自身のPascalの評価は[11]で見つかる。私は文献を総括する意欲も能力も無い。この論文は私個人の観察を記録し、その大部分は必ず他の人の見解と重なる。私は、以下の問題点周辺に題材を編成する。
・型とスコープ
・コントロールフロー
・環境
・コスメティック
及び、多少重要度が下がるエリアについて。
始めから私の結論を述べる。初心者にプログラムの方法を教えるためにはPascalは立派な言語かも知れない。それについて私は直接の経験を持っていない。1968年では相当な業績だった。最近の言語設計に確かに影響を与え、その内でもAdaが最も重要だろう。しかし、その標準形式(現行と提案済みの両方)の中で、実際のプログラムを書くためにはPascalは適当ではない。環境とトリビアルなやり取りをし、他の誰かによって書かれたプログラムを使用しない、小さな自給自足プログラムだけのために適している。
2. 型とスコープ
Pascalは(ほぼ)強い型付け言語だ。大雑把に言えば、プログラムの各オブジェクトは、オブジェクトの正当な値とオブジェクト上のオペレーションを暗黙に定義する、上手に定義された型を持つことを意味する。コンパイルとランタイムチェックの或る混合によって、言語は不当な値とオペレーションを禁じることを保証する。勿論、コンパイラは言語定義の中で暗黙に示されたチェックの全てを実際にはしないかも知れない。更に、強い型付けは次元解析で混乱しない。人が以下のように'apple'と'orange'を定義するならば、
type
apple = integer;
orange = integer;
appleとorangeを伴なう任意の勝手な算術式は完全に正当である。
強い型付けはいろいろな方法で現れる。例えば、関数とプロシージャへの引数は正式な型マップのためにチェックされる。整数を期待するサブルーチンに浮動小数点数を渡す、Fortranの自由は消えている。これは、確実にエラーを引き起す指示を警告するのだから、Pascalの望ましい特質であると私は考える。
整数型変数は正当な値の範囲を持つと宣言されるかも知れない。コンパイラとランタイムサポートは、小さなものしか保持しない変数に大きな整数を置かないことを保証する。勿論ランタイムチェックはかなりな科料を払うけれども、これもまたサービスのようであるらしい。
型とスコープの幾つかの問題に移ろう。
2.1. 配列のサイズはその型の一部である
人が以下のように宣言するなら、
var arr10 : array [1..10] of integer;
arr20 : array [1..20] of integer;
arr10とarr20は各々10と20の整数の配列だ。整数配列をソートするためのプロシージャ'sort'を書きたいとしよう。arr10とarr20は違う型を持っているので、両方をソートする単一のプロシージャを書くことは可能ではない。
これが特に'Software Tools'、そして一般的にプログラムに影響すると私が思う処は、ソーティングのような共通的、一般的な目的のオペレーションをするためのルーティンのライブラリを作るのを実に困難にしていることだ。
最も常に影響を受ける特別なデータタイプは'文字の配列'で、Pascalでは文字列は文字の配列だからである。文字列aの中で文字cが最初に現れる位置又は無ければ0を返す関数'index(s,c)'を書くことしよう。'index'の文字列引数を処理する方法が問題だ。文字列の長さが違うので、'index('hello',c)'と'index('goodbye',c)'の呼出し両方を正当には出来ない(出来ないのだから、'hello'のような固定文字列の終りをどうやって感知するかの疑問は省く)。次に試みることは、
var temp : array [1..10] of char;
temp := 'hello';
n := index(temp,c);
しかし、'hello'と'temp'は違う長さなので、'temp'への代入は不当だ。
この無限の後退から抜け出す唯一つの手段は、各可能な文字列の長さのためのメンバーを持つルーティンのファミリーを定義することか又は、文字列すべて('define'のような固定文字列を含む)同じ長さにすることである。
後者のアプローチは、2つの大きな邪悪のうち、ましな方だ。'Software Tools'では、'string'と呼ばれるデータタイプは、
type string = array [1..MAXSTR] of char;
として宣言され、ここで定数'MAXSTR'は"十分に大きく"、そして、全てのプログラムの全ての文字列は正確にこのサイズだ。プログラムを走らせることが可能だけれども、これは理想からは程遠い。便利なルーティンの本当のライブラリを作る問題の解決にならない。
固定サイズの配列表現が簡単には受入れられない幾つかの状況がある。例えば、テキストの行をソートするための'Software Tools'プログラムは、収容出来るだけ多くの行を持つメモリを満杯にすることによって作動する。その動作時間は、どのようにメモリを目一杯にパックするかに依存する。
従って、'sort'のためには別の表現が使われる。文字の長い配列と、この配列へのインデクスのセットである。
type charbuf = array [1..MAXBUF] of char;
charindex = array [1..MAXINDEX] of 0..MAXBUF;
しかし、固定長表現を処理するために書かれたプロシージャと関数は可変長形式と一緒には使えない。全く新しいルーティンのセットは、この表現の中で文字列のコピーと比較をすることが必要とされる。Fortran又はCでは、同じ関数が両方に使われるだろう。
上で示したように、固定文字列は、
'this is a string'
として書かれ、'packed array [1..n] of char'の型を持つ。ここでnは長さである。従って、異なる長さの各文字列リテラルは異なる型を持つ。メッセージをプリントし、クリーンアップするルーティンを書く唯一つの方法は、全てのメッセージを同じ最大長へ膨らませることだ。つまり、
error('short message ');
error('this is a somewhat longer message');
多くの商用Pascalコンパイラは、問題を明示的に避ける'string'データタイプを与える。'string'は長さと無関係に同じ型に取られる。これは、この単一のデータタイプの問題を解決するが、他はそうではない。固定文字列の長さを計算するような二次的な問題を解くことも失敗する。別のビルトイン関数が通常の解法だ。
Pascalの熱狂ファンは屡々、配列サイズ問題にうまく対処するためには、幾つかのライブラリルーティンをコピーし、プログラムのパラメータに手で記入しなければならないだけだと主張するが、その弁明は良く言っても弱い(12)。
"配列の境界はその型の一部(又はもっと正確に、そのインデクスの型)であるので、異なる境界を持つ配列に適用するプロシージャ又は関数を定義することは不可能だ。この制約は深刻なものに見えるかも知れないけれども、私達がPascalで持っている経験は、それは殆ど発生しないことを示すようだ。[...]しかし、パラメトリック配列のサイズをバインドする必要は、プログラムライブラリの使用に関して深刻な欠陥である。"
この言い損ないがPascalについて最も大きな一つの問題だ。フィクスされるのであれば、その言語はとてつもなく便利になるであろうと私は信じる。提案されたPascalのISO標準(13)はそのようなフィクス("順応な配列スキーマ")を与えているが、標準のこの部分の承認は明らかに依然としてどうなるか分からない。
2.2. 静的変数と初期化が無い
'静的'変数(よくAlgolが盛んな処では'それ自身の'変数と呼ばれる)は、あるルーティンにプライベートで、ルーティンの一つの呼出しから次の呼出しまで、その値を保持するものだ。事実上Fortranの変数は、COMMONを除いて内部的に静的だし、Cでは、ローカル変数に適用出来る'static'宣言がある(厳密に言えば、Fortran 77では、静的属性を強制するにはSAVEを使用しなければならない)。
Pascalはそんなストレージクラスを持っていない。Pascal関数又はプロシージャが一つの呼出しから別の呼出しまで値を記憶するつもりならば、関数又はプロシージャに対して、使用される変数は外部でなければならないことを意味する。従って、他のプロシージャに可視でなければならず、更に大きなスコープでは、その名前は一意でなければならない。問題の簡単な実例はランダムナンバージェネレータだ。現行の出力に使用された変数は次のものを計算するため保存されなければならない。だから、ランダムナンバージェネレータの呼出し全てを含むライフタイムの変数に格納されなければならない。実際には、これは典型的にプログラムの最も外側のブロックだ。従って、そのような変数の宣言は、それが実際に使われている場所からは非常に離れている。
一つの実例は'Software Tools'の第7章に書かれたテキストフォーマッタから来る。変数'dir'は、進路理由付けの間、交互に左右を獲得するため、超過ブランクが挿入された処からの方向をコントロールする。Pascalでは、そのコードは次のようである。
program formatter (...);
var
dir : 0..1; { direction to add extra spaces }
.
.
.
procedure justify (...);
begin
dir := 1 - dir; { opposite direction from last time }
...
end;
...
begin { main routine of formatter }
dir := 0;
...
end;
変数'dir'の宣言、初期化、使用がプログラム全体に散らばっていて、文字通り何百行と離れている。C又はFortranでは、'dir'は、それを知る必要のあるルーティンのみへプライベート化出来る。
...
main()
{
...
}
...
justify()
{
static int dir = 0;
dir = 1 - dir;
...
}
勿論、もっと大規模で同じ問題の他の実例も多くある。バッファされたI/Oの関数、ストレージ管理、シンボルテーブル、すべてが心に浮かぶ。
少なくとも2つ関連する問題がある。Pascalは静的に変数を初期化する方法(すなわち、コンパイル時に)を持たない。FortranのDATAステートメント又は以下のようなCでの初期化子
int dir = 0;
に類似なものはない。
これは、Pascalプログラムが変数初期化のために明示的な代入ステートメントを含まなければならないことを意味する(以下のように)。
dir := 0;
このコードはプログラムソーステキストを大きくさせ、ランタイム時プログラム自体を大きくさせる。
更に、静的ストレージクラスの欠如により引き起こされる大き過ぎるスコープの問題を、初期化子の欠如はより悪化させる。初期化すべき時は開始時であり、メインルーティン自体が多くの初期化コードで始まるか又は、初期化するための一つ又はそれより多くのルーティンを呼ぶ。どちらの場合でも、初期化されるべき変数は可視でなければならず、それは階層の高いレベルで有効である。その結果は、初期化されるべき任意の変数はグローバルスコープを持つということだ。
3番目の困難は、最小共通先祖又はそれより上の先祖で宣言されていなければ、2つのルーティンが変数を共有する方法が無いことである。FortranのCOMMONとCの外的且つ静的ストレージクラスの両方が、先祖の情報の共有無しに、2つのルーティンがプライベートに協調する方法を与えている。
新しい標準は静的変数、初期化、又は非階層型コミュニケーションを提供していない。
2.3. 関連プログラムコンポーネントは分離されなければならない
元々のPascalはワンパスコンパイラで実装されていたので、言語は使用前の宣言に信頼を置く。特に、プロシージャと関数は、それらが使用される前に宣言されていなければならない(本体とすべて)。その結果は、典型的なPascalプログラムはボトムアップから読む。つまり、全プロシージャと関数が、任意のコードがそれらを呼出す前に、全てのレベルで表示される。これは、関数が設計され使用される順序と本質的に反対だ。
CやRatforの#include機能のようなメカニズムにより、ある程度これは緩和される。すなわち、プログラムをごった返しすること無しに必要な処でソースファイルはインクルードされる。UCB、VU、Whitesmithsのコンパイラ全てが#includeを与えているけれども、標準Pascalの一部ではない。
Pascalでは本体から関数又はプロシージャのヘッダーの宣言を分離することを許す'先行'宣言もあり、それは相互再帰プロシージャを定義するために意図されている。本体が後で宣言される時、その宣言上のヘッダーは関数の名前だけを含んでもいいが、最初のインスタンスからの情報を繰り返してはならない。
関連する問題は、Pascalが宣言を喜んで受入れる厳密な順序を持つことだ。各プロシージャ又は関数は
label ラベル宣言(あれば)
const 定数宣言(あれば)
type 型宣言(あれば)
var 変数宣言(あれば)
procedureとfunction宣言(あれば)
begin
関数又はプロシージャ本体
end
から成る。
これは、プログラムを分り易くするため論理的に関連あるものを纏めたいとプログラマが思っている時でも、コンパイラの便宜のために、一つの種類(例えば、型)の全宣言は共にグループ化されなくてはならないことを意味する。プログラムはコンパイラに全て一度に送られなければならないから、宣言、初期化、型の使用、変数を近くに纏めることは滅多に可能ではない。最も熱心なPascal支持者の一部の人でさえ賛成している(14)。
"構造化された大きなプログラム内で、そのようなグループ化を出来ないことは、Pascalの最も不満な制約の一つである"
ファイルのインクルージョン機能は、ここでは少ししか役に立たない。
新しい標準は、宣言の順序についての必要条件を緩めていない。
2.4. 分割コンパイルがない
"公式"Pascal言語は分割コンパイルを与えていない。なので、各実装は何をすべきか自身で決定している。一部は(例えば、バークレイインタープリタ)は完全に許していない。これは言語の精神に最も近く、文字通り正確にマッチする。他の多くは、関数の本体が外部的に定義されていることを指定する宣言を与えている。いずれにせよ、そのようなメカニズム全てが非標準であり、異なるシステムは異なることをする。
理論的には分割コンパイルは必要でない。誰かのコンパイラが素晴らしく高速なら(且つ、全てのルーティンのソースが入手可能で、そのコンパイラがファイルインクルージョン機能を持ち、その結果ソースの複数コピーが必要でないなら)、すべての再コンパイルは同等である。勿論実際には、コンパイラはそんなに速くなく、ソースは屡々隠れ、ファイルインクルージョンは言語の一部でないので、変更は時間を喰う。
一部のシステムは分割コンパイルを許すが、境界を跨って型の一貫性を確認しない。これは強い型付けに大きな穴を作る(他の大部分の言語もクロスコンパイルチェックしないので、この点でPascalは劣っていない)。私は少なくとも一つの論文(幸いにも未発表だが)を見たことがある。その論文は、nページにて、分割コンパイル境界を跨ぐ型チェックにCが失敗することを厳しく非難し、一方n+1ページにて、Pascalとうまく付き合う方法は、型チェックを避けるためプロシージャを分割してコンパイルすることだと示唆している。
新しい標準は分割コンパイルを提供していない。
2.5. 型とスコープのその他の問題
以下の諸細目は小さないらいらだが、どこかでそれらに触れなければならない。
プロシージャのリテラルフォーマルパラメータとして非ベーシック型の名前付けは正当でない。つまり、以下が許されていない。
procedure add10 (var a : array [1..10] of integer);
それどころか、型名を案出して型宣言をし、その型のインスタンスであるフォーマルパラメータを宣言しなければならない。
type a10 = array [1..10] of integer;
...
procedure add10 (var a : a10);
当然、その型宣言は、それを使うプロシージャからは物理的に離れている。型名を案出する躾は、よく使用される型にはいいことだが、一度限りなら邪魔だ。
関数とプロシージャのフォーマルパラメータに対して宣言'var'を持つことはいいことである。プロシージャは、引数を変更するつもりであるとはっきり述べているからだ。しかし、呼ぶプログラムは、変数は変更されると宣言する方法を持たない。情報は一ヶ所のみにあるが、2ヶ所ならなお良い(しかし、パン半分でも無いよりはましだ。Fortranはユーザに、誰が変数に対して何をするのか、何も語らない)。
デフォルトでは配列は値渡しであることも少し面倒だ。その実質的結果は、全ての配列パラメータはプログラマにより大概考え無しで'var'と宣言されるというこである。もし'var'がうっかり省略されるなら、出力されるバグは難解だ。
Pascalの'集合'概念は、便利な記法と何らかの自由な型チェックを与えるので、いい考えだ。例えば、
if (c = blank) or (c = tab) or (c = newline) then ...
のようなテストの集合は、
if c in [blank, tab, newline] then ...
として、もっと明快に、おそらくもっと効果的に書ける。しかし、集合型は強く実装依存(多分、オリジナルのCDC実装が59ビットだったから)なので、実際には集合型はこれより以上には役立たない。例えば、関数'isalphanum(c)'(``is c alphanumeric?'')を、
{ isalphanum(c) -- true if c is letter or digit }
function isalphanum (c : char) : boolean;
begin
isalphanum := c in ['a'..'z', 'A'..'Z', '0'..'9']
end;
と書くのは自然である。だが、多くのPascal実装(オリジナルを含めて)では、集合が単に小さ過ぎるというだけで、このコードは失敗する。従って、ポータブルなプログラムを書く積りなら、集合を一般的に使用しないのが最もいい(この明快なルーティンも、範囲テスト又は配列参照を使うよりも、集合を使う方が断然に遅く走る)。
2.6. 脱出がない
必要な時、型メカニズムをオーバーライドする方法、Cにおける"cast"メカニズムのようなものが無い。これは、ストレージアロケータ又はI/OシステムのようなプログラムをPascalで書くことは可能ではないことを意味する。返すオブジェクトの型を話す方法が無く、そのようなオブジェクトを他の使用のために任意の型に強制する方法が無いからである(厳密に言えば、可変レコード付近の型チェックに大きな穴があり、それ以外は不正型チェックメカニズムは得られる)。
3. コントロールフロー
Pascalのコントロールフローの欠乏は小さいが、多い。致命点への一撃よりも、何百の傷の死亡である。
論理的演算子'and'と'or'の評価順序に保証が無い。つまり、Cでの&&と||のようなものは無い。他の大部分の言語でもそうだが、この欠点はループコントロールに殆ど打撃を与える。
while (i <= XMAX) and (x[i] > 0) do ...
は、x[i]の前にiがテストされる保証が無いのだから、極端に賢くないPascalの使用である。
ところで、このコードでの括弧は必須だ。言語は関係演算子を最下位に4つのレベルの演算子優先順位しか持たない。
ループを抜けるのに、'break'ステートメントがない。これは、構造化プログラミングの支持者によって採用された哲学、一つのエントリ-一つの出口に忠実であるが、特に論理式が評価される順序のコントロール不能が連結している時、気色悪い遠まわしな又は重複したコードに導く。この同じ状況を、C又はRatforで記述を考えよう。
while (getnext(...)) {
f (something)
break
rest of loop
}
'break'ステートメントが無い場合、Pascalでの最初の試みは、
done := false;
while (not done) and (getnext(...)) do
if something then
done := true
else begin
rest of loop
end
だが、'getnext'の次の呼出しの前に、"not done"を評価を強制させる方法が無いのだから、これは動かない。幾つかの失敗の後、以下に帰着する。
done := false;
while not done do begin
done := getnext(...);
if something then
done := true
else if not done then begin
rest of loop
end
end
勿論、常習犯はループを抜け出すのに'goto'とラベル(数字だけで、予め宣言しなければならない)を使える。そうでなければ、早期抜け出しは苦痛であり、ほぼいつもブーリアン変数とある量のずるを要求される。Ratforで配列内のブランクでない最後を見つける探索と比較せよ。
for (i = max; i > 0; i = i - 1)
if (arr(i) != ' ')
break
Pascalを用いると
done := false;
i := max;
while (i > 0) and (not done) do
if arr[i] = ' ' then
i := i - 1
else
done := true;
'for'ループのインデクスはループの外側で定義されていないから、最後まで行ったのかそうでないのか知ることは可能ではない。小さな制約として、'for'ループのインクリメントは+1又は-1のみ出来る。
再びワンイン-ワンアウトの理由のため、'return'ステートメントが無い。擬似バリューに値をセットすることにより関数返り値は返されれ(Fortranでするように)、関数の最後に落ちる。これは時には、固有の値を持つ関数の終りへ全パスが行き着くことを保証するための歪みをもたらす。多くの実装が緊急終了を引き起す'halt'を与えているけれども、最も外側のブロックへ行き着くことを除いて、実行を終了させる標準の方法も無い。
'case'ステートメントはCよりも良く設計されている。但し、'default'句が無く、入力式がどのケースにもマッチしなければ、その振舞いは未定義である。このきわめて重大な省略は'case'概念をほぼ無意味にしている。'Software Tools in Pascal'の6000行を超える中で、'default'があったならば、'case'は少なくとも一ダースの場所で役に立ったであろうに、私は4回しか使わなかった。
新しい標準はこれらの点に関して何の救済も無い。
4. 環境
Pascalのランタイム環境は比較的希薄で、おそらく"公式"言語内のソースレベルのライブラリを除いて、拡張メカニズムは無い。
PascalのビルトインI/Oは受けて当り前の悪い評判を持つ。レコード指向の入力及び出力に強く価値を置いている。インタラクティブ環境の真当な実装を困難にする先読み様式も持っている。基本的に、I/Oシステムは処理されるレコードの一つ先のレコードを読まなければならないと信じていることが問題である。これは、インタラクティブシステムにおいて、プログラムが走る時、その最初のオペレーションは、プログラム自身がやることの前に、入力の最初の行のためにターミナルを読もうとすることを意味する。だが、プログラム内では
write('Please enter your name: ');
read(name);
...
先読みは、入力のためのプロンプトのプリントの前に入力を待っているので、プログラムはハングする。
もっと注意深い実装によって、このI/O設計の酷い結果の大部分から抜け出すことは可能であるが、全てのPascalシステムがそうとは限らないし、何れにせよコストがかかる。
I/O設計は、Pascalが設計されたオペレーティングシステムを反映している。その欠点ではないとしているけれども、Wirthでさえ偏りを認めている(15)。テキストファイルはレコード、すなわちテキストの行から成ると仮定されている。行の最後の文字が読まれる時、ビルトイン関数'eoln'は真となる。その時点で、新しい行の読込みを始め、'eoln'をリセットするために'readln'を呼ばなくてはならない。同様に、ファイルの最後の文字が読まれる時、ビルトイン関数'eof'は真となる。どちらの場合も、'eoln'と'eof'は、後ではなく、'read'の前にテストされなければならない。
これを考えれば、賢い入力をシミュレーションすることで相当の苦痛が取り除かれるはずである。この'getc'の実装は、バークレイとVUのI/Oシステムで動くが、他の全てで必ずしも動くとはならないかも知れない。
{ getc -- read character from standard input }
function getc (var c : character) : character;
var
ch : char;
begin
if eof then
c := ENDFILE
else if eoln then begin
readln;
c := NEWLINE
end
else begin
read(ch);
c := ord(ch)
end;
getc := c
end;
ENDFILEとおそらくNEWLINEは'char'変数に対して不当な値ではないので、型'character'は'char'と同じではない。
各プログラムを始める'program'ステートメントの中の(有効な)論理的ユニット番号により名前付けられた定義済みファイルを除いて、ファイルシステムへのアクセスの考えは全く無い。これは、Pascalが元々開発されたCDCバッチシステムを明らかに反映している。ファイル変数
var fv : file of type
は、非常に特別な種類のオブジェクトだ。割当ても出来なければ、'eof'、'eoln'、'read'、'write'、'reset'、'rewrite'('reset'はファイルを巻き戻し、再読み込みを容易にする。'rewrite'は書くためのファイルを用意する)のようなビルトイン関数への呼出しを除いて、使用も出来ない。
Pascalの多くの実装が、外部環境からの名前によってファイルへのアクセスを許す回避手段を与えている。だが、便利でも標準でもない。例えば、多くのシステムが、'reset'と'rewrite'の呼出しの中でファイル名引数を許す。
reset(fv, filename);
しかし、'reset'と'rewrite'はプロシージャであって、関数ではない。ステータスの返りは無いし、ある理由のためアクセスが失敗すればコントロールの回復方法が無い(UCSDシステムは通常アボートを無力にするコンパイルフラグを用意している)。そして、
reset(fv, filename);
if fv = failure then ...
のような式にfvが出現出来ないのだから、その方針でも脱出が無い。この拘束は、ファイル名のミススペル等から回復するプログラムを書くことを本質的に不可能にしている。'Software Tools'の改訂版で、私は十分に解決しなかった。
再度、おそらくPascalのバッチシステムの起源を反映しているので、コマンドライン引数にアクセスする考えは無い。環境に非標準のプロシージャを加えることによって、ローカルルーティンはアクセス出来る。
Pascalでは多目的ストレージアロケータを書くことが可能ではないので(そのような関数が返す型について語る術を持たない)、言語はヒープ上にスペースを割当てる'new'と呼ばれるビルトインプロシージャを持つ。定義された型のみが割当てられるので、例えば、文字列を保持するために任意サイズの配列を割当てることは可能でない。'new'によって返されるポインタは周辺に渡されてもいいが、処理されない。つまり、ポインタ算術が無い。ストレージが尽きると、コントロールを回復する方法が無い。
新しい標準はこれらの分野に何の変更も無い。
5. コスメティック
経験あるプログラマにとって、これらの問題の大部分がうんざりで、おそらく初心者にとっても迷惑だろう。だが、共有しよう。
他のAlgolに啓発された言語も共通に、Pascalはセミコロンをターミネータ(PL/IやCではターミネータ)と言うよりもむしろセパレータとして使う。結果として、ステートメントが厳密にセミコロンを置くことに人は道理にかなう洗練された考えを持つに違いない。おそらくもっと重要なことは、適切な処にセミコロンを置くことについて人が真剣であれば、相当量の迷惑な編集作業が必要である。プログラムで最初の場面を考えよう。
if a then
b;
c;
しかし、bの前に何かを挿入しなければならないなら、今やbは'end'に先行するので、セミコロンはもはや必要でない。
if a then begin
b0;
b
end;
c;
今、'else'を追加するなら、'end'のセミコロンを削除しなければならない。
if a then begin
b0;
b
end
else
d;
c;
そして、等々。プログラムの発展に伴って、セミコロンのさざ波が上下する。
プログラマの心理に一般的に受入れられている実験結果は、セパレータとしてのセミコロンはターミネータとしてのセミコロンよりも10倍エラーしがちであることだ(Pascalに基づく最も重要な言語Ada(17) では、セミコロンはターミネータである)(16)。幸い、Pascalでは、人はほぼいつも目を瞑り、ターミネータとしてのセミコロンを隠し通せる。例外は宣言のような場所にあり、そこでセパレータvs.ターミネータ問題は少なくても真剣ではない。そして'else'の直前だが、憶えているのは容易だ。
CとRatforプログラマは、{と}に比べて'begin'と'end'を嵩張っていると感じる。
関数名それ自体で、その関数の呼出しだ。関数の名前を知っている場合を除いて、そのような関数呼出しと簡単な変数を区別する方法が無い。Pascalは、関数名に関数内の変数かのように振舞わせるFortranのトリックを使う。但し、Fortranでは関数名は実のところ変数であって、式の中に出現出来るが、Pascalでは式の中の出現は再帰呼出しである。fが引数無し関数なら、'f:=f+1'はfの再帰呼出しだ。
演算子は少ししか無い(おそらく、優先順位レベルの少なさに関係する)。特に、ビット操作演算子(AND、OR、XOR、等)が無い。私は、次のようなトリビアルな暗号化関数をPascalで書こうとしたが、諦めた。
i := 1;
while getc(c) <> ENDFILE do begin
putc(xor(c, key[i]));
i := i mod keylen + 1
end
賢い'xor'を書けなかったからだ。集合型は少しは役立つ(言ってみれば)が、十分ではない。Pascalがシステムプログラミング言語と主張する人は一般的にこの点を見逃している。例えば、[18, p. 685]
"Pascalは現時点[1977]でシステムプログラミングとソフトウェア実装のためには公的分野で最も優れた言語である"
は少し世間知らずだと思われる。
おそらくPascalは文字列の中に埋め込まれたクオートを指し示すためにダブルクォートを使うので、ヌル文字列は無い。
'This is a '' character'
文字列の中に非グラフィックシンボルを置く方法が無い。もっとはっきり言えば、標準言語のどの部分もそれらに言及していないから、非グラフィック文字は強い意味で非人間的だ。改行、タブ等のような概念は各システム上"特殊"な規則(普通は文字集合、例えばASCIIの改行は10進数値10を知っていること)で処理される。
マクロプロセッサが無い。明白な定数を定義するための'const'メカニズムは、Cでの簡単な#defineステートメントの使用の約95%を担当するが、もっと込み入ったものは絶望的だ。Pascalコンパイラにマクロプリプロセッサを載せることは確かに可能である。これは、
#define error(s)begin writeln(s); halt end
のような賢い'error'プロシージャをシミュレーションすることを可能にする('halt'は結局のところ、一番外側のブロックへの分岐として定義されるかも知れない)。それから、
error('little string');
error('much bigger string');
のような呼出しは、'writeln'(標準Pascal環境の一部として)は任意のサイズの文字列を取扱えるから、動く。この便利なものを可能にしてルーティンにする方法が一般的に無いことは不幸だ。
言語は宣言での式を禁止しているので、
const SIZE = 10;
type arr = array [1..SIZE+1] of integer;
のようなことを、又はもっと簡単なものでさえ、
const SIZE = 10;
SIZE1 = SIZE + 1;
書くことは可能でない。
6. 展望
'Software Tools'の中のプログラムを書き直す苦心は1980年3月に始まり、やったりやらなかったりして、1981年1月まで続いた。最終製品(19)は1981年6月に刊行された。その間、私は徐々にPascal(コスメティック、コントロールフローの不完全)の表面に順応し、重要なもの(配列サイズ、ランタイム環境)に対して不完全な解法を開発した。
本の中のプログラムは、トリビアルでない仕事をする完全な、うまく工作されたプログラムになっているはずである。だが、それらは十分である必要は無く、複雑にオペレーティングシステムと相互作用もしていない。だから、私は非常に急場しのぎの解法(実際のプログラムのためには働かない)で通り抜けられた。
PascalがCより優れていると私が感じた重要な方法は無いが、Ratforより明らかな改善がある処が多くある。最もはっきりとしているのは再帰だ。多くのプログラムが、再帰的書かれている時(取り分けパターン検索、クイックソート、式評価)にずっと明快である。
列挙データ型はいい考えだ。それらは、正当な値の範囲の境界を定め、ドキュメント化する。レコードは関連する変数のグループ化に役立つ。ポインタは比較的殆ど使用しないことが分かった。
ブール型変数はブール条件のための整数よりもいい。Fortranの論理変数は駄目な設計なので、元のRatforプログラムは幾つか不自然な考えを含んでいた。
プログラムを書いている最中に手の間違いをたまにPascalの型チェックは警告した。値のランタイムチェックも随時、エラー特に指標範囲違反を報告した。
ネガティブな面に振り返ると、ソースの単一行を変えるため最初から大きなプログラムを再コンパイルすることは非常に骨が折れる。型チェック有り又は無しの分割コンパイルは大きなプログラムには必須だ。
Pascalの文字列と非グラフィックの扱いは非常に不適当なので、文字はPascalの一部でありFortranの一部でないという事実からの恩恵を受けなかった。両方の言語では、キーワード、エラーメッセージ、その他類似のテーブルのためのリテラル文字列を初期化することは、甚だしく下手くそである。
仕上がったプログラムは概してRatfor同等とほぼ同じ行数である。私の予断ではPascalは口先が多くて式が少ない言語だったから、最初これは私を驚かせた。ループ制限や指標のような処にPascalは任意の式を許可し、Fortran(すなわちポータブルFortran 66)はしていないのが本当の理由らしい。だから、幾つかの不要な代入は削除出来る。更に、Ratforプログラムは関数を宣言し、Pascalはしない。
終わるに当たって、Pascalに不利な場合の主なポイントを纏めさせて欲しい。
1. 配列のサイズがその型の一部であるので、多目的なプログラム(すなわち、異なるサイズの配列を処理すること)を書くことは可能でない。特に文字列処理は非常に難しい。
2. 静的変数、初期化、プログラム(変数は本来よりもずっと大きいスコープを要求する)の"地域"を殺すための非階層的組合のコミュニケーション方法の欠如。
3. 言語のワンパス性質は、プロシージャと関数が不自然な順序で送られることを強制する。様々な宣言の強制分離は、論理的に同じ所属であるプログラムコンポーネントをあちこちに散らかす。
4. 分割コンパイルの欠如は、大きなプログラムの開発の邪魔をし、ライブラリの使用を不可能にする。
5. 論理式の評価の順序をコントロール出来ない。それは、入組んだコードと異質な変数を導く。
6. 'case'ステートメントが、default句が無い故に骨抜きにされている。
7. 標準I/Oの欠陥。標準言語の一分として、ファイル又はプログラム引数を取扱う賢い用意が無く、拡張メカニズムが無い。
8. 大きなプログラムを編成するのに必要なツールの大部分(特にファイルインクルージョン)を言語は欠如している。
9. 脱出が無い。
最後のポイントがおそらく最も重要だ。言語は不十分だが、制限される。その制約を脱出する方法が無いからである。必要な時に型チェックを無力にするキャストが無い。"標準プロシージャ"を定義するコンパイラをコントロールしなければ、欠陥ランタイム環境を賢いもので置換える方法が無い。言語は閉鎖的である。
真剣なプログラミングのためにPascalを使う人は致命的な罠にはまる。
言語はとても重要なので、拡張されなければならない。しかし、各グループは各々の方向で、彼等が本当に欲しい言語なら何でも似るようにPascalを拡張している。分割コンパイルのための拡張、FortranライクなCOMMON、文字列データ型、内部静的変数、初期化、8進数、ビット操作等すべてが一つグループのための言語のユーティリティに追加している。しかし、他者へのポータビリティを殺している。
本来のターゲットを遥かに超えて何にでもPascalを使うのは間違っていると私は感じる。純粋な形式では、Pascalは玩具言語で、教育に適しているが、本当のプログラミング用ではない。
私がLinus氏のC++嫌いをこのところ取り上げているので、周辺には私がC++を嫌っているかのように思われてしまいました。しかし、何を隠そう、私はBjarne Stroustrup博士の大ファンであり、本当のプロフェッショナルなプログラマはLisp系とC/C++をやっている(只単にやっているのみならず、セキュアで立派なプログラムを書けることを意味します)人達であると思っているくらいです。それ以外の言語は所詮オマケ的存在で、それぞれ用途に適した言語を使えばいいと思います。そのオマケ内でも私がPerlを好んでいるのは自由度が高く、汎用的であるからです。
ところで、Stroustrup博士の最新著"Programming: Principles and Practice Using C++"を読み終えたのですが、この本のみならず博士の英文は非常に読み易いです。どこかのブログでラテン語を使い過ぎなどと自分の無知を棚に上げて悪口をほざいている馬鹿がいましたが、決してそんなことはありません(と言うか、私の周辺の英語圏の人に聞いてみると、そのラテン語から来た英単語はジュニアハイ入学前後までに子供ですら知っているくらいです)。正直言って、下らんハウツーものや「やさしい何とか」シリーズなんか読むよりも遥かにこの本をお薦めしたいと思います。博士の信念が本全体に漲っています。博士の信念は"プロフェッショナルなプログラマは本当の意味でのプロフェッショナルになれ"であると私は解釈していますが、その意味でこの本は易しさとか短期間とかの軽薄短小を完全に無視しています。
私が本を選ぶ時はいつも序文を読みます。これは論文で言えばアブストラクトにあたると思いますが、本でも論文でも何でも(ソフトウェアドキュメントもそうです)最初のはしがきに何か注目すべきことを書いていないものは大抵つまらないし、読む価値はありませんでした。はしがきがつまらないものを誰が本文まで読みたいと思いますか?
博士の最新著は現在日本語訳が進行中らしいです。私は誰が訳者でどこの出版社が刊行するのかも知りませんし、興味もありませんが、幸いにも博士は本のサンプルとして序文をhttp://www2.research.att.com/~bs/programming.htmlで公開しています。この序文がまた博士の面目躍如な内容でして、これを参考にして日本語版を待つか原書をすぐにでも購入するか判断してもいいのではないかと思います。その私訳を以下に載せておきますが、翻訳権を侵害するとかそういう気は毛頭ありません。また少しでもその種の話が持ち上がればすぐに削除しますし、遅くても日本語版が出版され次第、削除します。要はあくまで販売促進のために勝手連がやっていると思ってください。尚、以下の私訳では謝辞とか本の使い方とかの本質的に関係のない部分は省いています。
Programming: Principles and Practice Using C++ 序文
Bjarne Stroustrup
プログラミングは、問題の解決法を記述する技術であり、その結果コンピュータがそれらの解決法を実行出来る。プログラミングでの大部分の苦心は解決法を見つけ精錬することだ。しばしば、解決法をプログラミングする過程を通してのみ、問題は充分に理解される。この本は、以前にプログラムをしたことはないが、難しい学習を喜んでやる人のためのものである。原理を理解し、C++プログラミング言語を使って実践的なプログラミングのスキルの会得を手助けする。私の目的は、最新のベストなテクニックを使って、簡単で便利なプログラミング作業を実行するための、充分な知識と経験を取得させることである。どれくらいの期間がかかるのか? 大学コースの第一学年の一部としては、半学年でこの本を通して出来る(平均的難易度の四つのコースの学習量を持っていると仮定して)。自学なら、それより短くなると期待してはならない(多分14週間で、1週あたり15時間だろう)。
3ヶ月は長いように思えるかも知れないが、学ぶべきことが沢山あって、それから最初の簡単なプログラムを約1時間で書くだろう。また、すべての学習は漸次的である。すなわち、各章は新しい便利なコンセプトを紹介し、実世界での使用にヒントを得た例をつけて描いている。進むに連れて、コード内でアイデアを記述する技能は、コンピュータにして欲しいことを実行させながら、徐々に且つ確実に増加する。私は、「1ヶ月かかる理論を学び、それからそれを使用出来るか見せよ」とは決して言わない。
何故プログラムしたいのか? 私達の文明はソフトウェア上で走っている。ソフトウェアを理解しないと、「魔術」を信じるはめになり、最も興味深く有益で社会的に便利な仕事の分野の多くから締め出されるだろう。私がプログラミングについて語る時、GUIのあるパーソナルコンピュータアプリケーションから、エンジニアリング計測や組込みシステムコントロールアプリケーション(例えば、デジタルカメラ、自動車、セルフォーン)や、人文系やビジネスに見られるテキストを処理するアプリケーションまでを考えている。数学と同様に、プログラミングは(ちゃんとうまく行くとき)私達の考える能力を研ぎ澄ませる価値ある知的な行為である。しかし、コンピュータからのフィードバックのお蔭で、大概の数学公式よりもプログラミングは具体的で、より人に受け入れ易い。手を伸ばして世界を変える(理想的には良い方に)一つの方法である。最後に、プログラミングは非常に楽しいだろう。
何故C++なのか? プログラミング言語無くてはプログラムを学べないし、C++は実世界のソフトウェアで使用されている主要なコンセプトとテクニックを直接サポートする。C++は最も広く使用されているプログラミング言語の一つであり、アプリケーションの卓越した範囲で見られる。海底から火星の表面までのどこでもC++アプリケーションが見つかる。C++は非プロプライエタリ国際標準により正確且つ包括的に定義されている。すべての種類のコンピュータで、良質で且つ(又は)フリーな実装が入手可能である。C++を使って学ぶプログラミング概念の大部分は、例えばC、C#、FortranやJavaのような他の言語のうちで直接に使用出来る。最後に、エレガントで充分なコードを書くための言語として、私は単にC++を好んでいる。
これは、プログラミングを始めるに最も易しい本とはならない。だが、文字通りのことを意味しない。実世界のプログラミングの基礎を学べる最も易しい本を目標としているだけである。多くのモダンなソフトウェアが数年前は高度であると考えられたテクニックに依存するので、それは全く野心的な目標だ。
私の根本的仮定は、貴方が責任を持って立派なレベルのシステム品質を与える、他人の使用のためのプログラムを書きたいと思うことである。すなわち、貴方がプロフェッショナリズムのレベルに達したいと思っていることを仮定する。従って、単に教え学び易いことではなく、実世界のプログラミングを始めるために必要なことをカバーするトピックスをこの本のため選んでいる。貴方が基礎的な作業を正しく行うテクニックを必要とするなら、私はそれを描き、そのテクニックをサポートするに必要な概念と言語の便宜を示し、そのための演習を与え、それらの演習に貴方が取組むことを期待する。貴方がオモチャプログラムを理解したいなら、私が示すよりもずっと少ない進歩を得られる。他方で、ぎりぎりの実践的重要性の題材で貴方の時間を無駄にはしない。ここでアイデアを釈明するならば、貴方がほぼ確実に必要とするであろうからである。
どのようにして事を為しているか理解せず、且つコードに著しい追加を自分でしないで、他人の仕事を使うことに貴方が欲するならば、この本は貴方のためにはならない。そうならば、他の本と他の言語でより良いサービスを受けられるかどうか考えてほしい。それが貴方のプログラミングの見解にほぼ近いのならば、その見解を持った所から本当に貴方のニーズに適合しているか再考してほしい。人々はよく価格と同様にプログラミングの複雑さを過小評価する。貴方が必要とすることと、私が描くソフトウェアの現実の部分が合致しないが故に、プログラミングを嫌いになることを私は残念に思う。プログラミングの知識を要求しない「インフォメーションテクノロジー」な世界に大いに関係がある。この本は、トリビアルでないプログラムを書きたい又は理解したい人に役立つことを目的とする。
その構成と実践的目的のために、この本は、C++を少し知っている又は他の言語でプログラムしているがC++を学びたい人のためのプログラミングに関するセカンドブックにも成り得る。貴方がそれらのカテゴリの一つに合致するなら、この本を読むのに貴方がどれくらい時間がかかるか推測するのを差し控えるが、多くの演習をすることを勧める。より適切な処でより新しいテクニックを採用することよりもむしろ、古く馴染んだスタイルでプログラムを書く共通の問題を消すのに役立つ。貴方がもっと伝統的な方法の一つでC++を学んだことがあるなら、第7章に届く前に驚き役立つものを見つけるだろう。貴方の名前がStroustrupでなければ、私がここで議論していることは「貴方の父C++」ではないのだ。
プログラミングはプログラムを書くことによって学習される。この意味では、プログラミングは練習要素のある他の鍛錬に似ている。本を読むだけでは、泳ぐこと、楽器を弾くこと、車を運転することを学べない。貴方は練習しなければならない。多くのコードを読み書き無しではプログラムも学べない。この本は、説明のためのテキストとダイアグラムに密接に結びついたコード実例をフォーカスする。プログラミングのアイデア、概念、原理を理解するため、及びそれらを記述するために使用される言語構成概念をマスターするために、実例を必要とする。それは本質的であるが、それ自体ではプログラミングの実践的スキルを与えない。そのため、演習をし、プログラムを書き、コンパイルし、走らせるツールに馴れなければならない。間違いを拵え、それらを直すことを学ばなければならない。コードを書くための代わりは無いのだ。更に、それが楽しいところでもある!
他方では、少数のルールに従い、マニュアルを読むことよりもプログラミングにはずっと多くのことがある。この本は断固として「C++文法」に焦点を当てていない。根本のアイデア、原理、テクニックを理解することが良いプログラマの本質だ。上手く設計されたコードのみが、正しくて信頼出来、保守し易いシステムの一部になるチャンスを持つ。また「根本的なもの」は継続する。つまり、今日の言語とツールが進化又は置換えられた後でも、まだ本質的であるだろう。
コンピュータサイエンス、ソフトウェアエンジニアリング、インフォメーションテクノロジー等についてはどうか? それはすべてプログラミングか? 勿論そうではない! プログラミングは、コンピュータ関連分野のすべてに基礎となる根本的トピックスの一つであり、コンピュータサイエンスのバランスのとれたコース内に自然な場所を持つ。私は、主要な概念と、アルゴリズム、データ構造、ユーザインタフェース、データ処理、ソフトウェアエンジニアリングのテクニックの簡単な紹介を与えている。しかし、この本は、それらのトピックスの完全なバランス良い学習の代用ではない。
コードは便利と同時に美しい。この本はそれを分かるのを助けるため、コードが美しいとは何なのか理解するため、原理をマスターし、そのようなコードを造るための実践的スキルを獲得することを助けるために書かれている。プログラミングに幸運あれ!
Linus氏のgoto有用論やC++否定論を始めて聞いた時、私は正直、氏なら言っても可笑しくないと思いました。それは、氏がC言語をアセンブリ言語の代用として使っていることもありますが、gotoがどうのこうの、OO指向言語がどうのこうの、そんなみみっちい議論よりも前に、Linus氏のOS設計思想から万事すべてが端を発しているからです。Tanenbaum氏との論争、すなわちマイクロカーネル対モノリシックカーネル論争におけるLinus氏の考え方が氏のすべてなんです。その内容については、氏の半生記"Just for Fun"の"Minix vs. Linux"の節(原書p98から記述あり)や、他の書籍にも載っているし、論争の舞台であったニュースグループのアーカイブズもオンライン上にありますから、詳細を知りたければそれらを見ていただくとして、ここでは繰り返しません。
しかしながら、私の周辺の人達とよもやま話をした時、この論争自体があったことすら知らない人もいました。さすがにLinus氏を知らない人はいませんでしたが、Tanenbaum氏を知らない人が結構いて、話を続けようにもどうしても途切れてしまい、Linus氏のgoto有用論やC++否定論の根本がどこにあるのか、触れずじまいになりました。今やTanenbaum氏のかの有名なOS教科書の存在すら知らない人が殆どを占めるのです。OS開発に従事したことが無くても業界人や情報数理系学生の常識(本当に読んだかどうかは別にして)だと私なんかは思っていたのですが、最早そういう時代ではないのです。
ところで、Linuxがブレークして随分時間が経った2006年にも、Tanenbaum氏はマイクロカーネルの優位性を主張する論文を発表しました。その経緯を簡単に纏めたのがTanenbaum氏の"Tanenbaum-Torvalds Debate: Part II"というエッセイです。(尚その中で、少しばかりLinus氏がOO指向を毛嫌いにする要因にも触れております。)とは言っても、結局はマイクロカーネルとモノリシックカーネルのどちらが優れているか(理論上ではなく実践的に)はもっと後世の人が判断することになるのでしょう。
その私訳を以下に載せておきます。
TanenbaumとTorvaldsの論争パートⅡ
2006年5月12日 Andy Tanenbaum
始めに
再びのマイクロカーネル論争の時のように見えるらしい。技術的議論に入る前に、私は言いたいことがある。多くの人が、Linusと私が敵か何かのようなことを言ったり、示唆した。それは完全に出鱈目である。私は彼と一度会った。彼は愛想が良く、非常に賢明だ。私達はいくつかの技術的問題について反対だが、それが敵同士にはならない。考え方の不一致を個人的な不和と混同しないでほしい。私はLinusに対して何もないし、彼の成し遂げていることに大いに敬意を持っている。
あり得ない事であるが、2、3年前マイクロソフトはKen Brownという男を雇い、Linusが私のMINIX 1システムからLinuxを盗んだとする本を書かせた。Linusの名声を台無しにする言いがかりに、私は強く反駁した。私はLinuxの設計に完全には賛成でないかも知れないが、LinuxはLinusの子供であり、私の子供ではないし、Brownが盗用だと言った時、大変残念だった。
詳細に入る前に、私の関心はマイクロカーネル自体にあるのではないことも言いたい。私の関心は高信頼度な(そしてセキュアな)オペレーティングシステムであり、マイクロカーネルはそのための良いツールであると考える。これについて以下で詳しく述べる。
魅了する話
「魅了する話の前には、頭を働かせよ」
と読める標識がコロラドの米空軍アカデミーにあるらしい。私はそこに実際にあるのか知らないが、何事においてもいい考えだと思う。
マイクロカーネルが如何に遅いか、マイクロカーネルが如何にプログラミングが難しいか、如何に商業的に使用されていないかについてや、他にも大量のナンセンスが長年スラッシュドットのようなフォーラムに絶えずポストされて来ている。事実上、これらのポストのすべてが、マイクロカーネルが何なのかまたは何が出来るのか知らない人から来ている。そのようなポストをしている人々が始めにマイクロカーネルベースのオペレーティングシステムを試し、「マイクロカーネルベースのOSを試したが、X、Y、Zを直接見た」のような信頼性を持つポストであれば、議論のレベルを高めるであろうにと私は思う。
最も簡単な方法は、MINIX 3をダウンロードし試すことだ。フリーで、オープンソース(BSDライセンス)であり、一度CD-ROMイメージを持てば、CD-ROM上に焼き、そこからブートし、ルートとしてログイン出来る。しかし、何か実際的なことをするためには、ディスクパーティション(1GBになるだろう)を割当て、MINIX 3をそこにインストールする必要がある。どうか最初にセットアップマニュアルをプリントし、読んでほしい。インストールは10分くらいかかるだろう。そして、セットアップマニュアルに書かれている通りに、パッケージすべてをCD-ROMにインストールする。今やXをスタートさせ、実体験を得られる。マニュアル通りに、全オペレーティングシステムをリビルドしてみてほしい。その全ビルドは、カーネル+ユーザモードドライバーとユーザモードサーバ(全部で125コンパイル)が約5から10秒かかる。
MINIX 3は祖父のMINIXでないことに注意してほしい。MINIX 1は教材として書かれた。教材としては大学で尚も広く使用されている。Al Woodhullと私はMINIX 1についてテキストブックすら書いた。MINIX 3は、それプラス、高信頼度で自己回復可能な、そして肥大化無縁のオペレーティングシステムの始まりであり、第3世界の子供達を助ける100ドルラップトップのようなプロジェクトや、もしかして組込みシステムにもおそらく有用だ。MINIX 1とMINIX 3は、Windows 3.1とWindows XPが関係があるのと同じ意味で、関連性がある。つまり、ファーストネームが同じだ。従って、学内にいる時MINIX 1を使っていても、MINIX 3を試してみよ。驚くだろう。最小だが、X, bash, pdksh, zsh, cc, gcc, perl, python, awk, emacs, vi, pine, ssh, ftp, the GNU tools及び400を超える他のプログラムの付いている役立つUNIXシステムなのである。それがすべて、ちぽっけなマイクロカーネルをベースにして、直ぐにでも利用可能だ。
だから、「Tanenbaumがマイクロカーネルは非常に素晴らしいと思うなら、何故彼はマイクロカーネルベースのOSを書かないのか?」といったようなコメントを繰り返さないようにして貰いたい。私は書いた(実際は、私、私の学生、私のプログラマでやった)。LinuxまたはBSDのようには、まだ完成または成熟していないが、小さく理解し易いマイクロカーネルに基づくユーザ空間内に、信頼出来る、自己回復可能なマルチサーバUNIXクローンの実装が実現出来ることをはっきりと示している。未成熟(一年ちょっとの期間で3人でやった)をマイクロカーネルに関する議論と混同するな。時間の経過に伴って、更にフィーチャーを追加出来るし、する積りだし、更にソフトウェアをポートする(貴方の支援を大いに歓迎する)。2005年10月のMINIX 3サイト発進以降、40万を超える訪問が記録されている。貴方自身で試してみよ。
論文
最近、私の博士課程の学生Jorrit Herder、私の同僚Herbert Bosと私は、"信頼出来、セキュアなオペレーティングシステムを作れるか?"と題する論文を書き、IEEE Computer Societyの主要発行誌であるIEEE Computerマガジンに提出した。2006年5月に受領され刊行された。この論文の中で、大部分のコンピュータユーザのためには、パフォーマンスよりも信頼性がもっと重要であると論じ、オペレーティングシステムの信頼性の改善を試みている現行4つの研究プロジェクトについて議論している。それらのうち3つがマイクロカーネルを使っている。IEEEはその論文をウェブサイトに載せている。そして、誰かがそれへのリンクをスラッシュドットにポストし、昔のマイクロカーネル対モノリシックシステムの議論を再熱させた。私はその論文に喜んで共著したことを認める一方で、絶対に「Linuxは時代遅れ」議論の再現を予期しなかった。
それから、Linusは回答したが、それは私達が別の小さな論争を持とうとしているかのように見える。いいだろう。だが、どうか皆さん、技術的議論を貫こうではありませんか。
Linusの主張
Linusの根本的見解は、マイクロカーネルが分散アルゴリズムを必要とし、それらは扱いにくいということだ。Maarten van Steenと私はそれらを扱う本を書いたけれども、分散アルゴリズムが至って進展していないことを認める。私も過去10年、2つの分散システムAmoeba(LAN用)とGlobe(WAN用)を設計し、書き、リリースして来た。分散アルゴリズムの問題は、失う可能性のあるメッセージを参照する共通した時間の欠如と、リモートプロセスが死んでいるのか又はただ遅れているのかについて不確実性があることだ。単一のマシン上のマイクロカーネルベースのオペレーティングシステムに対して、これらの問題は当てはまらない。従って私は、分散アルゴリズムが難しいという点ついてはLinusに賛成である一方で、それは当面の議論には無関係である。
更に、ユーザ空間のコンポーネントの大部分はドライバーであり、それらは直接にサーバとやり取りする。キャラクタデバイス・ドライバーは厳密に同じプロトコル(バイトストリームを読み、書く)に従い、ブロックデバイス・ドライバーは厳密に同じプロトコル(ブロックを読み書きする)に従う。ユーザ空間のサーバの数は相当に小さい。すなわち、ファイルサーバ、プロセスサーバ、ネットワークサーバ、リインカーネーションサーバ、データストア、その他諸々である。各自はきちんと定義された仕事を持ち、他のシステムときちんとやり取りする。例えばデータストアは、有効なサーバの緩い連結のために、公開又は予約購読サービスを与える。サーバの数は将来的にも増えることはない。その複雑性は全く御し易い。これは推論ではない。私達は何と言っても既に実装している。MINIX 3をインストールし、コードを検証してみなさい。
Linusはまた、共有データ構造は素晴らしい考えであると指摘した。ここは、私達は反対だ。貴方がオペレーティングシステムの教科コースを取ったことがあるのであれば、教科コースで如何に時間を取って、テキストブックで如何にスペースを割いて、相互排他と協調プロセス同期に割当てていたか、憶えているはずである。2つまたはそれより以上のプロセスが同じデータ構造にアクセス出来る時、自分の首を絞めないように、貴方は非常に注意しなければならない。セマフォ、モニタ、ミュテックス、その他素晴らしい道具を使っていても、うまくやるのは非常に困難である。
私の見解は出来る限り共有データ構造を避ける方がいい。システムは、他者から内部構造を完全に隠蔽する小さなモジュールで構成されるべきである。他のモジュールが仕事依頼出来るために「薄い」インターフェースがうまく定義されていなければならない。それがオブジェクト指向の肝心なところ、つまり情報を隠蔽し、共有しないことなのである。(Dave Parnas風に言えば)情報隠蔽は素晴らしいと私は考える。インターフェースを変えない限り、システムの正しさに影響することなく、貴方の意のままにデータ構造、アルゴリズム、モジュールの設計を変えられることを意味する。ソフトウェアエンジニアリングのすべての教科コースはこれを教えている。ところが実際は、Linusは、オブジェクト指向プログラミングの過去20年間の業績を心得違いだと言っている。私はそれは買わない。
各モジュールに他のモジュールのデータ構造に立入らないようにさせるならば、次の論理的ステップは、このルールをMMUハードウェアに強制するための異なるアドレス空間内に各モジュールを配置することである。これを一オペレーティングシステムに適用する時、マイクロカーネルと、メッセージと上手く定義されたインターフェースとプロトコルを使用してコミュニケーションするユーザモードプロセスの集まりを得る。ずっと明快に、もっと保守し易い設計に近づいている。当然、Linusはモノリシックカーネルでの経験で理由付けしていて、疑いも無くマイクロカーネルまたは分散システムに詳しくない。私自身の経験は、自分で設計し、実装し、複数のマイクロカーネルベースのオペレーティングシステムをリリースしたことに基づいている。これが、私達に何が困難で何がそうではないのかについて異なる大局的見方を与えている。
信頼出来るオペレーティングシステムの問題点全般の異なる見地として、Jonathan Shapiroによる"Debunking Linus's Latest"と題されたエッセイを見られたい。
マイクロカーネルは現実なのか?
一言で言えば、イエスである。フォーラムにおいて「マイクロカーネルがそれ程よいなら、何故無いのか?」という、終りなきコメントがスラッシュドット・マンディ(8日月曜日)に殺到した。実際にあるのだ。MINIX 3の他に以下がある。
・QNX
・Integrity
・PikeOS
・Symbian
・L4Linux
・Singularity
・K42
・Mac OS X
・HURD
・Coyotos
QNXは実際の商業システムに広く使用されている。例えばCiscoのtop-of-the-line routerはそれを使っている。私は、Ciscoがパフォーマンスに非常に気を使っていることを保証する。
信頼性が絶対的基準である、軍隊及び航空宇宙市場での主要なオペレーティングシステムの一つが、別のマイクロカーネルであるGreen Hills' Integrityだ。
PikeOSは、国防、航空宇宙、自動車、及び産業アプリケーションで広く使用されるマイクロカーネルベースのリアルタイムシステムである。
Symbianはまた別の人気あるマイクロカーネルで、主にセルフォーンで使用される。しかしながら、Symbianは純粋なマイクロカーネルではないが、ハイブリッドである。カーネル内にドライバーがあり、ファイルシステム、ネットワーキング、テレフォニーがユーザ空間にある。
私は引き続きどんどん進められるだろうが、明らかに信頼性とセキュリティがミッションクリティカルであるアプリケーションにおいては、設計者は殆どマイクロカーネルベースのOSを選んでいる。Linusは個人的に組込みリアルタイムシステム(ここでは、パーフォーマンス、信頼性、セキュリティが最高である)に関心無いだろうが、これらの市場は膨大で、参加企業はマイクロカーネルが、この目的を達成する方法だと考えている。
PCの世界を見れば、L4Linuxがあり、ドレスデン技術大学のHermann Härtigグループによって書かれた。数パーセントのパフォーマンス損失を伴いながらも、L4マイクロカーネルの上に、ユーザ空間内にLinuxのすべてを走らせている。マイクロカーネルを使用することが、ドレスデン技術大学の人達に、L4の上にDROPS(リアルタイム)、NIZZA(セキュリティ)のような新システムの構築をさせているのであるが、これら新フィーチャーのために変更の必要性無しでフルになおもLinuxにアクセスを持っている。このようにして、彼等は新しい機能を実験し、なおもレガシィプログラムを走らせている。他のグループも、組込みシステムでレガシィアプリケーションをサポートするために設計された擬似仮想LinuxであるWombatのように、オペレーティングシステム研究のためL4マイクロカーネルを使用している。
別のL4ベースのOSがTUD-OSであり、他にもある。
マイクロソフト社もマイクロカーネルに興味を持っている。他人事でなく、モノリシックカーネルの保守が頭痛の種であることを理解している。Windows NT 3.1はマイクロカーネルでの気乗りしない最初の試みだったが、上手く行かず、1990年代初期のハードウェアではパフォーマンスが充分ではなかった。そして、しばらくはそのアイデアを断念した。だが、最近モダーンなハードウェア上で再度試み、その結果がSingularityだった。今私は、多くの人がマイクロソフト社がやるのであれば、きっと馬鹿に違いないと決めてかかったのだが、Singularityプロジェクトを率いた人達、Galen HuntとJim Larusは非常に賢明な人達であり、Wnidowsが滅茶苦茶な状態であり新しいアプローチが必要であることをよく理解していることを知っている。Vistaで働いている人でさえ、問題があることを認識し、私の提唱する通り、ユーザ空間にドライバーを移動している。
約10年前、IBMは非常に大きい顧客のための、新しい高パフォーマンスオペレーティングシステムをスクラッチから開発を始めた。そして、明白な設計目標は、システムの機能をカーネルからサーバ及びアプリケーションプログラムへ移すことであったが、マイクロカーネルに類似である。K42と呼ばれるこのシステムは、米国エネルギー省やその他に配置されている。
Mac OS Xは多少マイクロカーネル的である。内部的には、改良されたMach版マイクロカーネルの上にBerkeley UNIXが載っている。カーネルモードですべてが走るので(若干のパフォーマンスのために)、本当のマイクロカーネルではないが、何年か前にカーネギ・メロン大学はユーザ空間内のMach上で走るBerkeley UNIXを持っていた。だから、L4Linuxと同様に若干のパフォーマンスの損失があれど、おそらく再度出来るであろう。本当のマイクロカーネルシステムのため、Apple BSDコード(Darwin)をL4へポートするWorkが進行中である。
掛け声は高く実行は伴っていないけれども、GNU HURDもマイクロカーネルをベースとしている。実のところ、それには2つがある。一つ目はMachをベースとし、二つ目はL4をベースとした。三つ目のバージョンは、おそらくまた別のマイクロカーネルCoyotosをベースとするであろう。emacs、gccその他広く使用されているソフトウェアの作者であり、GPLの創始者で、権威のあるMacArthur天才賞受賞者でもあるRichard StallmanによってHURDは設計された。
開発中の別のマイクロカーネルはEROSの後継のCoyotosである。ここでは、信頼性よりもセキュリティに焦点があるが、膨れ上がったカーネルが両方の面で問題があるという意味で、その二つの問題は関連がある。
私は、XenとTrangoのようなハイパーバイザを議論さえしていない。これらは多くの意味でマイクロカーネルと全く異なっているが、カーネルモードでは小さなコードのみ走るという特徴を共有しており、信頼出来るセキュアな構築の鍵となると私は信じる。
MINIX 3、QNX、Integrity、PikeOS、Symbian、L4Linux、Singularity、K42、HURD、 Coyotosやその他は折衷的な束であるが、マイクロカーネルの中の何かを見ようとするのは私一人ではないことは明らかだ。マイクロカーネルが左程広く使われていない理由を不思議に思っているなら、システムには多くの惰性があるからである。何故LinuxまたはMac OS XがWindowsを置換していないのか? つまり、システムには多くの惰性があるからだ。ブラジルの大部分の車が自家製のエチルアルコールで走り、だからブラジルでは乗り物の燃料を比較的殆ど使わない。何故米国がそうしないで、一触即発の中近東への依存を減らそうとしないのか? システムには多くの惰性があるからだ。たとえ優れた実践であっても、人々に変えさせることは非常に難しい。
私は何を実証しようとしているのか?
実際には、MINIX 3と私の研究は一般的にはマイクロカーネルについてではない。それは高信頼度で自己回復なオペレーティングシステムの構築である。リセットボタンのあるPCを造る製造業者が何処にもいないのであれば、私は仕事を終わらせようと考えている。テレビはリセットボタンを持たない。ステレオはリセットボタンを持たない。車はリセットボタンを持たない。それらはソフトウェアのフル実装であるが、ソフトウェアを必要ではない。ソフトウェアが多くクラッシュするのであるから、コンピュータはリセットボタンを必要とする。コンピュータソフトウェアは車のソフトウェアと違うことを私は知っているが、ユーザはどちらも動いて欲しいのであって、ソフトウェアが車を動かしコンピュータを動かさないはずである理由の講義を欲しく無い。コンピュータのライフタイムより、故障間隔期間がずっと長いオペレーティングシステムを私は構築したい。それによって平均的なユーザはクラッシュの経験を持たない。MINIX 3は多くの特別な信頼性フィーチャーを持つ。私達は決して成し遂げていない(例えば、バーチャルメモリは今年後半の課題にある)が、信頼性の改善が現時点でオペレーティングシステム設計者に差し迫っている最も大きな変更であると思う。平均的ユーザはハードウェアからのフィーチャーまたは搾取には気も止めないが、コンピュータを時間の100%完璧に動かすこととクラッシュが無いことに気を止める。お祖母さんに聞いてみなさい。
それでは、マイクロカーネルがこの目標でしなければならないことは何か? マイクロカーネルは自己回復システムの構築を可能にする。それが私の関心事であり、私の研究対象である。OSの大部分をユーザプロセスの束(一つは各ドライバーと様々なサーバのためにある)に移すことは、コードのバグの数を減らさないが、深刻なダメージを与える各バグの力を大いに低下させ、信頼出来るコンピューティングベースのサイズをも減らす。私達の設計では、大部分のドライバーが失敗する時、リインカーネーションサーバが新鮮なコピーをリスタート出来るし、オプション的にデバッギングのために死んだドライバーのイメージをセーブし、イベントを記録して、電子メールをアドミニストレータまたはデベロッパに送る等が出来る。システムは走り続け、最悪でも作業またはデータの損失無しで優雅にシャットダウンする。リインカーネーションサーバ自身、ファイルサーバ、プロセスサーバのような、いくつかのコンポーネントはクリティカルであり、それらを失うことはシステムをクラッシュさせるが、システムを完全破壊する、欠陥のあるオーディオ、プリンタ、またはスキャナドライバーを許す理由は全く無い。それらはリスタートさせるべきであり、作業は続行すべきである。自身の失敗を感知し修理出来るシステムが私達の目標だ。マイクロカーネルではこれが容易に出来る。ワシントン大学の研究者がNooksで素晴らしい仕事をしているし、カールスルーエ大学の一グループも仮想マシン技術で興味ある仕事をしているけれども、モノリシックカーネルでは非常に困難だ。
LinuxについてLinusが語る
最近の更新。LinusはLinuxがひどく肥大化して来ているという認識に至っている。彼のコメントはここで見られたい。
宿題
マイクロカーネルは何が出来て何が出来ないかを独善的に述べる前に、 MINIX 3を得て試してみて、知識有る独善者になれ。貴方の信用度を落とすことになる。MINIX 3の設計について更に勉強するためには、IEEE Computer5月号の論文、USENIX ;loginにある"MINIX 3でのモジュラーシステムプログラミング"、または"信頼出来ないデバイスドライバーにも関わらず、信頼出来るオペレーティングシステムを構築するための軽量メソッド"を見られたい。
ここまでお付き合いしてくださり、感謝します。
新年早々のMatt S. Trout氏のエッセイ"Love your idiots"は言葉はキツイですが、コミュニティでの在り方を考えさせる味わい深いものです。どのコミュニティでも最低一人はこういう役割の人がいないとうまくいかないと私は思います。何故なら、そのコミュニティが全員超天才だったら、早々とコミュニティは崩壊してしまうことは明らかだと思います。以下、その私訳を載せておきます。
汝の馬鹿を愛せよ
2010年1月3日 Matt S. Trout
プロジェクトマイルストーンと思考モデル
そう、私は最近オープンソースプロジェクトのマイルストーンについてずっと考えている。コードベースのマイルストーンではないが…プロジェクトの利用とコミュニティから見たマイルストーンだ。普通は貴方が造れると言うよりも、出くわすものである。
しかし、今日私が話したいものは、苦くて甘い瞬間だが、貴方のプロジェクトを盛んにしたいのなら、正しく扱う必要があるものである。
馬鹿の到来
今までに貴方はその瞬間を知っているはずだ。貴方は、最初の何人かのコミッターを得て、幾つかのリリースをし、このコードを製品に採用する、実にクレージーな(だが、素晴らしい)幾人かの人達を見たことがある…そして今、全くアホみたいに意味のない質問をする野郎が出現し、貴方の考えることは「嗚呼、こいつ本当の馬鹿だ。こいつ何を言っているんだぁ?」となる。
まぁ…いいかい、その野郎は馬鹿だろう。だが、彼等は問題について貴方のやり方とは違った考えをしているのだから、何の意味も無いことは確実である。私が言いたいのは…メーリングリストまたはIRCを見つけるために、彼等はドキュメントを充分に読めたのである。だから、彼等は全くの無知ではない。しかも、彼等はフルタイムのプログラマでないかも知れず、関心事の一貫とした思考モデルを得る方法を彼等は知ってはいるが、それを自分でコードすることが難しいとか何とかかも知れない。
ポイントは、この人間とコミュニケーションを取る方法を見つける必要があることだ。貴方のプロジェクトが、問題領域について同じ思考モデルを持つ人のみに使用されるなら、潜在するユーザと寄与者ベースを著しく制限している。おそらくもっと大切なのは、
異なる思考モデルを持つ人にアクセスされるプロジェクトのためには、彼等にとって意味のあるドキュメントを彼等が書けるようにドキュメント寄与者を貴方が引寄せて保有する必要がある。
または、短いが礼儀を欠いた言い方では、
ドキュメントを他の馬鹿に理解させるために必要とすることを、馬鹿のみが語れる。
だから…会話を練り直せ。違う方法で説明せよ。最後には断固とした一つを得るだろう。これはおそらく時間がかかる。それでもいいのだ。腹立たしいが、真に迫った概念上のバリアを横切ろうとしているのであるから、時間がかかることを避けることは出来ない。幸いにも努力の価値ある魔法の公式がある。
「これはいくつか説明が必要だ。君が分かるまで私がやれば、君はそれをドキュメントのために書いてくれるかね?」
彼等が「はい」と言っている限り、やる意欲が定まっているのであるから、彼等がいつやるのかは全く問題ではない。そして、彼等は貴方の言う事に非常な注意を払うことを意味し、それは貴方も注意を必要とする。ここで実際にしていることは、より良い説明の方法を得ることへ反復していることである。貴方はこの野郎に説明すら出来ないかも知れない。それでもいい。実験するためのもう一人の馬鹿が続けているだろう。
最後には、「おお、素晴らしい、有難う」と誰かに言わしめる、理路整然とした説明を生む程に、貴方はうまくなっているはずであり、更にドキュメントのパッチを得る。時間がかかるだろう。再度言うが、それでもいいのだ。一方で、貴方はもう一つの本当に重要な事実に偶然出くわすからである。
「馬鹿」は通常「私が充分にコミュニケーションを出来ていない人」の短縮形である。
その認識について最も面白いことは、これらの人々とコミュニケーションの方法を学ぶ必要があることではなく(実に重要なことではあるけれども)、
コミュニティのメンバとして馬鹿を引寄せ保有するならば…彼等は既に馬鹿を流暢に喋ることが出来、次の馬鹿を助けられることだ。
これの素晴らしい例が#dbix-class上の常連の一人である。私はその野郎を好きである。彼は、DBIx::Classを通して走らす多くのクエリが処理能力の限界であり、私の日常の仕事で見る多くのことよりももっと複雑なフィーチャーの一組を持つ大きなネットワークモニタリングシステムを保守している。彼は幾つかの点で明らかに賢明である。だが、私にPerlのことで訊く時は、相変わらず馬鹿のように思われる。私は彼がそうでないことを知っている。しかし、それは助けにもならない。何よりも問題はコミュニケーションのバリアであり、私の彼に対する怒りは自分の問題であり、彼ではないことを知っているので、自分の平静さを失わないため数日間、時々私は彼の振りをしたり、無視したりしなければならない結果となる。
しかし、数日で私達はうまくいく。彼に効果のある説明の組立を私が分かり彼に説明して、彼は「分かった」と笑って立ち去り、自分の必要とすることを実装する。それから数週間後に、私は別のユーザに同じことを説明して、怒りに怒っている自分を見る。すると、彼が介入して「Matt、心配いらない。俺が此奴を引取る」と言う。5分後には、その別のユーザは「分かった」と笑って立ち去り、彼等の必要とすることを実装する。そして、正直私は全然その説明が分からなかったので、自分の画面をまじまじと凝視するだろう。私には全くナンセンスだった。それでも、コンセプトをコミュニケーションすることにおいて完璧に成功したのだ。
可笑しいのは、その別のユーザにとっては、私がナンセンスだったのだ。もっとはっきり言えば、私は彼等を助けることにおいて完全完璧に役立たずだった。会話の最初に私が黙っていて、誰か他の人に説明させれば、結果的にもっと効果的であったであろう。私に手を貸し、引継いでくれる人が周辺にいたことは正に幸運である。そのことは、私を最後で重要なポイントへ連れ出す。
「馬鹿」は通常「私が充分にコミュニケーションを出来ていない人」の短縮形であり、彼等もまた貴方を馬鹿であると考えていることを意味する。
だから、汝の馬鹿を愛し、祝い、宴を設け、彼等に馬鹿で親しみ易いドキュメントを書かせよう。彼等をたむろさせ、彼等はうまく説明するのであるから他の馬鹿と話させよう。そして忘れてはならないことは、
貴方が思考モデルを持たないでコミュニティに足を踏み入れた日、貴方は馬鹿の一人になる。貴方が肥え太った偽善者にならないように、明らかな馬鹿のように行動せよ。
おっと、忘れてた、新年おめでとう。
明けましておめでとうございます。
さて、私の周辺では、先日に書いた"Gotoは有用である"について、折角のMatt S. Trout氏の本文よりも、何故か不幸にも私の書いた訳注の方が話題になっています。
Linus氏はgoto有用論の他にも、C++をボロクソに貶したり、本当に愉快な人だと私は思います。ただ、C++を貶された時の反応として、海外は置いとくとして、日本の一部の人が余りにも井の中の蛙的な反応をしたように記憶しています。代表的な例は「Linusはオブジェクト指向が分からないからだ」というのがあります。私は思わず笑いました。Linus氏ほどの頭脳で分からないことが、極東の日本の貴方がたは分かっていると言わんばかりじゃないですか。一部にせよ、だから日本は駄目だと思いました。つまり、自己を客観視せず、実質的実力は無いのに一流家気取りなんですね。もっとはっきり言えば何様のつもりなんだと。どうしたらこんな自惚れ屋になれるのかと思いました。
では、何故Linus氏はgoto有用論を打上げたり、オブジェクト指向を貶すのは何故か?
後で根拠を示しますが、Linus氏はプラットフォームに依存しないアセンブリ言語を求めているのであって、それが現状ではC言語なんです。Linuxカーネルのような究極の領域に、goto有害論やOO指向等甘ちゃん一般プログラマの常識なんて邪魔以外の何物でもないのです。究極のパフォーマンスを追い求めて日夜苦労している時に、何ら解決にもならないOO指向やC++なんて話を持込まれたら、Linus氏でなくとも怒りたくなるのは当り前だと思います。以上は私の勝手な推測ではなく、根拠はLinus氏の"Programming"にあります。その中で以下のことを言っているのです。
"Some people seem to think that C is a real programming language, but they are sadly mistaken. It really is about writing almost-portable assembly language,(以下略)"
「一部の人はCを実際のプログラミング言語だと考えているらしいが、彼等は決定的に間違っている。実は、ほぼ移植可能なアセンブリ言語を書いているのである」
結局、Linus氏から見れば、アセンブリ言語で書いているのに、構造化だとかOO指向だとか、全く悠長な貴族的趣味であり、可笑しいと映るのでしょう。但し、カーネルレベルを書くようなプログラマは、一般のプログラマよりも遥かに能力が高いのですから、その書くソースは非常に整然としていることは言うまでもありません(嘘だと思うなら、Linuxカーネルソースを少しだけ覗くことも無駄ではないでしょう)。
また、Linus氏はgoto有害論の故Djikstra氏の代わりにWirth氏を貶したと私は書きました。でも、真相はそんな簡単な話ではないのです。元々の故Djikstra氏が送ったノートの題名はそうではなかったのですが、学会誌に載った時に、"Go to statement considered harmful"となったようです。その雑誌の編集者がWirth氏だったのですが、事前にしろ事後にしろ執筆者の了解を得ずに出来ることとは思えませんし、故Djikstra氏は2001年にもなって、その舞台裏を暴露したのですが、それまでに数多くの栄誉を受けた後に言っても意味が無いと私は思います。Linus氏は故人に鞭打つのは忍びないと言っているのですが、これらのことも踏まえて発言しているように思います。
ところで、あの訳注を書くにあたっては、故Djikstra氏のEWD1308と番号付けられている'What led to "Notes on Structured Programming"'には勿論目を通しました。これこそが、(結末で)タイトルを変えたのはWirth氏であると暴露した手記なのです。年末年始の時期に、こういう歴史的文献を読むのも浮世離れしていいものです。私が調べた範囲では未だ和訳が無い(有名な文献ですが、和訳が無いことには驚かされました)のですが、何故かタイトルだけは『「構造化プログラミングに関する覚え書き」へと導いたもの』とネット上では流布されています。ちょっと私には違和感があるのですが、混乱を避けるため私もそれを踏襲します。以下、その私訳を載せておきます。
追記:1月2日
Linus氏がC++を貶したことに対する一部日本人の反応を手厳しく書きましたが、知人より書き過ぎではないかと忠告がありました。しかし、書き過ぎではありません。何故ならば、Linus氏に反駁したいならしたいで、内弁慶宜しく日本語で濁さないで何故英語で書かないのでしょうか。仮にLinus氏がそのページに辿り着いたとしても日本語をおそらくは読めないでしょうから、ある意味で確信的で卑怯だと言えます。ですから、日本国内向けにポーズさえ出来れば満足な輩に対しては、どう考えても自己愛に満ちているとしか言い様がありません。
「構造化プログラミングに関する覚え書き」へと導いたもの
2001年6月10日 Edsger W. Dijkstra
この歴史的ノートの目的は、1969年に私がEWD249「構造化プログラミングに関する覚え書き」を書いた時に、後から考えて私に影響を与えていると思われる体験を記述することである。私が憶えていることに基づいており、記憶が選り好みしていると確信するので、歴史の専門家の客観的事実を主張しないように。
私は、Wilkesによってケンブリッジ大学で開かれた1951年のサマースクールでプログラミングを紹介された。Wheeler、Gillと私は、1952年にアムステルダム大学の数学センターのパートタイム(当初は週に2日)のプログラマになった。週の残りは、ライデン大学で理論物理学を勉強した。
ケンブリッジ大学のEDSACのためのプログラム体系が、私の唯一つのモデルだった。アムステルダム大学のARRAのためのプログラム記法、入力、出力、ライブラリ体系を設計する時、私は徹底的にEDSACに倣った。次期後継マシンFERTA、ARMACとX1に対しても、プログラム記法と入力は同じパターンになるであろう。私は明らかに保守的なプログラマだった。その上に、ARMACの命令バッファはドラムの1トラックの容量しか無く、他の格納を破壊した。だから、「自動コーダー」のような冒険に私が乗出さなかったことを分かるだろう。
1955年、私は理論物理学者にならず、その代わりプログラマになる決心をした。理論物理学とプログラミングについて、プログラミングはより大きい知的な課題を含んでいると結論したので、私はその決定をした。ご存知の通り、その当時私は知的な慎み深さを持っていなかった。私は一級の科学者を装っておりプログラマになることは科学と離別のように思えたので、それは難しい決定だった。私の上司、A. van Wijngaardenにディレンマを説明した時、彼はコンピュータはここに設置することになっており、プログラミングの世界で、まだ足りない科学を作るために招聘される一人に私がなる可能性が高いと語った。ライデンでの学位授与は出来るだけ早くなることが正式に決まった(実のところ、私はもはやライデンでの歓迎を感じなかった。物理学者達は私を逃亡者として見なしており、数学者達は、その一人が自負して「勿論コンピュータなぞ何も知らん」という具合に、ただ軽蔑的であった)。
その間に、私とハードウェアの同僚Bram J. LoopstraとCarel S Scholtenの協力の形が現れた。次期マシンの機能仕様が書かれた(通常私によって)後、そのドキュメントは私達の間で一種の契約として働いた。ドキュメントは、何のマシンを設計し組立てるかを命じていたが、私がそのマシンのために基礎ソフトウェア全部を書いている間に予想出来たものは分かっていた。この作業分担の目標は、マシン組立の完了の時までに、私のプログラムが用意されることだった。
今振り返ってみれば、上述の段取りは、私がどのようにプログラマとして成長したかに計り知れない影響を持っていたことが分かる。まだ存在しないマシンに対してプログラムすることは全く通常であることも分かった。副産物として、原ドキュメントで仕様化されている抽象的なマシンにプログラムしたこと、及び実際のハードの部品に対してではないことが私の心深く刻み込まれた。原ドキュメントは説明書ではないが、指示書だった。食違いに関しては、テキストでなく、実際のハードウェアに落ち度があるであろう。
当時、私はこの作業分担と、存在しないマシンに対してのプログラミングの実施を全く通常であると考えた。後に、ソフトウェアがいつも遅れる理由についての、或米国人の記事を読んだ。利用可能なハードウェアに限りがあることが主な要因であることを読んだ時、私は非常に驚いたことを憶えている。私がプログラミングを学んだ環境は思ったよりも通常ではなかったと結論した。
勿論、設計からはタイポエラーや類似の欠点を締め出すことは出来なかったが、マシンがまだ用意されていない間、そんな不備は問題ではなかった。マシンの完了の後に、それらのエラーが出現するとすぐ容易に特定された。しかし、1957年にリアルタイム・インタラプトの導入とともに、私はこの最後の気休めな考え方を否定した。LoopstraとScholtenが私達の次のマシンX1に対するこの特徴を示唆した時、私のプログラムが再現性のないエラーの要因となっているという意見を受け、私はパニックになった。
結局、LoopstraとScholtenは意地を張っている私を煽てたので、私は彼等の提案を勉強した。最初に私が調査したことは、何事も無かったかのようにインタラプトされたプログラムが継続出来るような方法で、マシンの状態のセーブとリストアが出来ることを実証出来るかであった。私は代わりに出来ないことを実証した。二人は提案を変更せざるを得なかった。一般に認められているように、元の提案が失敗するようなシナリオは殆有り得ないが、これは実験よりも論拠を信頼しなければならないという私の信念を強くしたようである。7年後に私は新商用マシンのインタラプト・ハードウェアに欠陥を見つけるので、当時その信念は見たところ普及しなかった。
1959年、数学センターの同僚に次の問題を提起した時、私は非常に素晴らしい経験をした。その問題は、共有格納のアトミックな読込みと書込みを通して通信する2つのプログラムがあるとし、クリティカルなセクションの実行が時間内においては互いに排除するような方法で、その2つのプログラムをプログラム出来るか? 解法は押し寄せたが、すべて間違っており、人々は反駁のためにますます反撃例を工夫した。私はルールを変更せざるを得なかった。提出しなければならないプログラムの他に、その解法が正しい理由の論拠を課した。
2、3時間のうちに、Th. J. Dekkerが正しい論拠ともに解法を提出した。Dekkerは最初満たさなければならない証明を解析し、それらに合う論拠の形を選んだ。そして、その論拠が適用されるプログラムを構築した。数学の役割を後方立証に閉じ込める時を見失うことが如何に大きいかを示す大変明快な例であった。1968年、私は"A constructive approach to the problem of program correctness"と題された論文を発表する。
そして、そのうえALGOL 60があった。私達がそれを見たのは1959年で、1960年の前半には実装した。この実装はその時の私のプロジェクトで遥かに野心的だったので、私はひどく恐れた。ALGOL 60は、その設計者でさえ実装の方法を知らなかった時代までの先端であり、私はコンパイラを書いたことがなかったし、4096ワードのストレージしかないマシンで達成しなければならなかった(後者の制約は不幸に見えるが実は幸運だった。しかし、私は始めた時のことを憶えていない)。
今日の標準によれば、私達は自分達がやっていることを分かっていなかった。私達は実装に必要とされる理論的知識が欠如していることをよく分かっていたから、実装が正しいことの保証を望まなかった。私達はその環境下で出来得ることだけをした。すなわち、設計を出来る限り簡単かつ系統的にすること、考えられるミスを回避したかチェックすることだった。最後には、私達が想定しなかったミスがあり、最初に望んだほどクリーンではないので、コンパイラの修復を学んだ。私が1962年に数学センターを去った後にも、F. E. J. Kruseman Aretzはまだいくつかのエラーを見つけ修復した。
最初から、私達は2つの異なるエラーを予期した。書き間違い、この修復は簡単だ。
考え間違いは私達を製図板に戻した。人は異なる技術でそれらと格闘するから、その相違点は私達を助けた。J.A. Zonneveldと私は、互いに自分の前に自分のテキストがあるようにしながら、コーディングを一緒にすることで書き間違いと格闘した。コーディングを終わった時、両者のテキストは別々にパンチされ、2つのテープが機械的に照合され、約2ダース(記憶が正しければ)の相違が現れた。考え間違いは、互いに提案セクションが動く理由を確信させることで阻止に努めた。この思考過程において、私達は主にコンパイラの働きをディスカッションしたが、コンパイルされたプログラムはデータとして扱い、それが私達にプログラムテキストに特別な考慮は要らないことに慣れさせ、この経験は後で役立った。
経験全体が私を、モジュール化又は分割とルールと後に呼ばれるもの、または、インターフェース選びの注意と、概してプログラミング対象の潜在的スコープに対する抽象性を気付かせた。担当者の設計の正しさに信任を造ることは最も重要だったが、プログラマの作業面においては難しくするという私の次の見解に大きく寄与している。スピードに取り憑かれている世界では、これは一般的な概念ではなかった。
当時から憶えていて、今まで役立っている2つの原理は、
① かなり大きいプロジェクトを実際に始める前、特にコーディングの労力投入の前に、先ずはそのプロジェクトを没にし、そして、
② 最も困難な、最もリスキーなことから先ずは始めよ。
私の最初のテストプログラムはほぼ空のブロックだった。例えば、
begin real x end ,
これは最も難しくない例であるが、私の4番目のテストは2倍の合計を求めるものだった。その内側には合計ルーチンのネストされた呼出しが引数メカニズムを通して導入されたが、合計ルーチン自体は再帰的に定義されていた。次いでに言えば、Jensenのデバイスとして知られるようになるものの妥当性を実証した。
この実装の後、合間に私は、実に一般的な言葉で言えば、原則的に非同期なコンポーネントに適した整合の未解決な問題に戻った。直接関与せずに、X1に対するパンチカード機器のすべての類を束ねる試みを2、3年早く私は目撃し、リアルタイム・コミットメントの包含によって導入された複雑さの度合いに呆れた。平易さのために、私は代わって「タイムレス」設計を主張したが、その正しさは離散的推論のみによって確立されるだろう。
ほぼやむを得ず、協調連続プロセスのモデルが出現した。定義されていない(定義によって!)相対スピード付きの連続プロセスと、それらの協調のため、幾つかの同期プリミティブが付けられている。
単純化のためのもう一つの機会は、通信機器とそれを使うプログラムの間のタイミング面がその2つと完全対称であり、入力又は出力デバイスの有無と無関係であるという認識により出現した。言うまでもなく、この統合は大いに助かった。
私達がTHE Multiprogramming Systemに参加した時、規模拡大がゆっくりながらもますます明白な関心事となった。そうでなければならない。IBM内部では、そして可能なら他の所でも、「システムの複雑性」が非公式な意味でコンポーネント数の2乗に増大するであろうという自然仮法則が広まっていた。その背後の推論は簡単(各コンポーネントが互いに別のコンポーネントに干渉するであろう…)だが、それが事実なら、あるサイズを超えるシステムがデファクト標準になるであろう。これは、「システムの複雑性」が非公式の意味でサイズの一次より増大しないようなシステム構造に、私の興味を呼び起こした。1967年、「抽象層」という表現がコンピュータ用語に入った。
EWD123"Cooperating Sequential Processing"(September 1965)の最後の2つの文章を引用することにより、この議論を終わらせて欲しい。
「この論文が読者に、どんな種類の階層順序付けが妥当と期待されるか、明白な兆候を与えるならば、私は重要な目標の一つに到達している。そして私達は、多重プログラミングの複雑さとの対決が、単一プログラミングにとって大事なのは何かという明快な理解を私達に与えると思ってはいけない。」
1968年、私は深刻なうつにかかった。部分的に学部によって引き起こされた。学部は情報科学という相応しい呼称を受理せず、私の築いたグループを解体したのだった。そして、部分的に私自身の次に何をすべきか悩んだことにより引き起こされた。回顧すれば、ALGOLの実装とTHE Multiprogramming Systemは軽妙な練習に過ぎず、今や難しい事をする方法について実際の問題に取組まなければならないことを知った。うつ状態の中で、EWD249"Notes on Structured Programming"(August 1969)を(治療上の理由から)書くための勇気を集めるのに数ヶ月かかった。私の回復の始まりを意味した。
論文は、先立つ10年の(上述した)構成要素を合成している。早めのページには「プログラムの正しさの実証に繋がるプログラム構造」と言及し、知的支援として「1) 列挙、2) 数学的帰納法、3) 抽象化」にも言及している。一番目と最後について引用している(EWD249-14)。
「列挙的推論はそれが続く限り正しいが、私達は機転が利かないので、それ程続かない。列挙的推論は、非常に控えめに使用するという厳密な制限の許では、十分な知的ツールに過ぎない。列挙的推論における要求を減らすための主な知的技法として抽象化をきちんと認識すべきである。」
私は2つの外的刺激を受けたことがあった。1968年のガーミッシュ・パーテンキルヒェンにおける「ソフトウェア・エンジニアリング」についてのNATOカンファレンスと、「プログラミング方法論」についてのIFIPワーキンググループの設立である。至る所にあるゼロックス機のおかげで、私がタイプライターで書いたテキストは野火の如く広がり、コンピュータ雑誌Datamationの1968年IBM広告によって特徴付けられた流行の内に、人々は新鮮さを見つけたからであろうか、更に広がった。その雑誌は、PL/Iに切替えることによりプログラミング問題をすべて解決した光輝くSusie Mayerをフルカラーで演出した。明らかに、IBMは私のテキストの人気を嫌い、術語"Structured Programming"を盗み、IBMの援助の許でHarlan D. Millsは、元のコンセプトをgotoステートメントの廃止へと矮小化した。
振り返れば、その当時の公式数学に対する私の畏怖を観察出来るはずである。1970年、私は10年より長い展望を費やして、プログラミングは数学的分野に成り得るだろうし、成るべきであると論じた。数学的治療に良く順応させるように、プログラミング作業を再編成した。しかし、要求される数学を自身で造ることは注意して避けた。私はBob Floyd(基礎付けをした)、Jim King(私を確信させる最初の例を作った)、Tony Hoare(プログラムの概念の証明のために必要となる公理の観点から、構文がどのように定義されるかを見せた)を待たなければならなかった。そして、その時でさえ、彼等の仕事の重要性をすぐには分からなかった。私は実に遅いのだ。
最後に、記録のための短い物語。1968年、ACMコミュニケーションズは"The goto statement considered harmful"のタイトルの許で私のテキストの一つを発刊した。それは後年、残念なことに、そのタイトルより以外を見ようとしない著者達によって最も頻繁に参照され、テンプレートになることにより私の名声の要となった。私達は、"Dijkstra considered harmful"を含み、ほぼ任意のXについて"X considered harmful"というタイトルの記事を見るだろう。だが、何が起こっていたのか? 私は"A case against the goto statement"というタイトルで論文を提出した。その発刊を急ぐため、編集者は「編集者への手紙」の中に変えた。その過程で編集者は自身の創案の新しいタイトルを与えたのだ! その編集者はNiklaus Wirthだった。
あのMatt S. Trout氏の書くエッセイがこのところ全く禁止語一つ無く、愛読者である私から見れば嬉しいやら、若干の寂しさも(本当に勝手だなぁ)感じます。多分、クリスマスシーズンだったから、自粛されていたのではなかろうかと推察します。
ところで、年季の入ったPerlerでも意外に知らないものに、goto &subがあります。これはAUTOLOADで使われるし、確か弾さんも自身のブログで解説されていたようにも記憶していますが、知っていて損はございません。と言うか、Perlerと自称するならば、それくらい知っとけと言いたいくらいです。
Matt氏の最近のエッセイはそのgotoを親切にも”Goto considered useful”で解説しております。その私訳を以下に載せておきます。
Gotoは有用である
2009年12月26日 Matt S. Trout
違うよ、そのgotoでなくて、別のgotoだよ
この投稿は、以下のIRCでの会話によってヒントを与えられた。
02:28 <@mst> my key objection to 5.8.4 is that if you alter @_ and then goto it segfaults.
02:28 < dkulchenko> mst: you use goto?
02:29 <@apeiron> goto &sub is useful.
02:29 <@mst> dkulchenko: of course.
02:29 <@mst> dkulchenko: how else do you wrap import() routines and maintain the stack?
02:29 < dkulchenko> mst: i've been using perl for 6 years, and i have *no* idea what you just said :)
02:34 <@mst> you've seriously never seen goto &Foo::import; ?
02:34 < dkulchenko> nope
02:43 <@mst> it's not something you use very often
02:43 <@mst> but when you need it, you tend to need it
そう、6年のベテランがこのトリックを知らないなら、詳しく書く値打ちがあると思った。
しかし、最初に私は、伝統的なgotoと皆がそれをしない訳へ脱線する。次のコードを考えよう。
sub find_in_list {
my ($pattern, @candidates) = @_;
my $found;
foreach my $cand (@candidates) {
if ($cand =~ $pattern) {
$found = $cand;
goto DONE;
}
}
DONE:
return $found if $found;
return;
}
それが、時にはCプログラマに愛され、Djikstraによって憎まれ、どこのメンテナにも恐れられる伝統的なラベルスタイルのgotoである。なお、インターネットのどこかに、Cではいつも憎まれているわけではないことについて、Linusのがなり立てがある。ボクシングデイなので、ご自分でgoogleされたい。
[訳注:私は有名な訳者の中でも私が最も嫌うI氏のような端無いまねをしたくないのですが、昨今の単細胞な輩が増殖中なのを実感しておりますので、Linus氏の擁護のために訳注を書かせていただきます。私は勿論Linus氏のがなり立てがどこにあるか知っていますが、それは本文にある通り御自分で探して下さい。しかし、誤解が無いように少しばかりその概略を書きます。Linus氏が言うのは、カーネルレベルをやるようなプログラマなら、gotoの正しい使用が出来るはずであり、フローが明快であるような場所でgotoを使わないのは不自然である。goto有害論は洗脳されているのであり、それによって以来多くの言語設計者は頭がおかしくなっている。それはWirthのせいである(訳注の訳注:本当は、goto有害論を打ち上げたのはDjikstra氏なのですが、Linus氏がgoto有用論を持ち出した当時、Djikstra氏は亡くなられおり、故人に鞭を打つのは忍びないということで、まだお元気な、Djikstra氏の盟友Wirth氏を敢えて批判したのが真相です)。だから、gotoを使っていいのだ。但し、レベルが高い人に限る。と、まぁ以上のようなことをLinus氏は言っているのです。]
勿論Perlにおいては、そのような場合を扱う素晴らしいnext、last、redoがあるので、以下のように書ける。
sub find_in_list {
my ($pattern, @candidates) = @_;
my $found;
foreach my $cand (@candidates) {
if ($cand =~ $pattern) {
$found = $cand;
last;
}
}
return $found if $found;
return;
}
Perlが’last’を見る時、foreachループからの周回を短くする。勿論、’goto LABEL’が善くないことである一方で、ラベルそのものは必ずしも悪くない。ネスト化されたループがあるならば、どのループに影響するのか直に混乱するので、以下のようにラベルをループに付けることが出来る。
sub find_in_list {
my ($pattern, @candidates) = @_;
my $found;
CANDIDATE: foreach my $cand (@candidates) {
if ($cand =~ $pattern) {
$found = $cand;
last CANDIDATE;
}
}
return $found if $found;
return;
}
しかも、サブルーチンからの出口を一つより多く持つことに冷静かつ理性的な反対を持っていないのであれば、以下のようにラベルは一つも必要ではない。
sub find_in_list {
my ($pattern, @candidates) = @_;
foreach my $cand (@candidates) {
return $cand if ($cand =~ $pattern);
}
return;
}
そして、終にはList::Utilのfirstルーチンは、このサブルーチン全く書く必要が無いことを意味する。
my $found = first { /$pattern/ } @candidates;
以上で、’goto LABEL’が無くてもいいと分かったので、脱線を終了し、’goto &sub’へ戻ろう。それは正確には何をするのか?
ところで…’goto &sub’と’sub(...)’の違いは、初期システムにおける’goto’と’gosub’の違いに非常に似ている。すなわち、サブルーチンを呼ぶことは、後でどこにいるのかを返すことなのだ。gotoはそれをしない。
もっと正確に言えば、’goto &sub’はPerl空間での現行サブルーチン呼出しをgoto先のサブルーチンで置換える。しかも、そのコールスタック上の場所も消え去る。これの意味することは、
sub call_me {
return (do something with @_);
}
sub call_it_1 {
call_me(@_);
}
sub call_it_2 {
goto &call_me;
}
があるならば、call_it_1とcall_it_2の呼出しの結果は同じである。但し、call_meサブルーチンの内部において違いがある。call_it_1について、call_meはcall_it_1に呼ばれたことを知っている。しかし、call_it_2を使うならば、call_meは、call_it_2それ自体ではなく、call_it_2と称される何でもいいものから呼出されたと思うだろう。
それでは、何故これが問題なのか? 私が今まで経験したことで二つの理由がある。それらはどちらも「そこにいないように装いたい」に要約される。すなわち、問題は、ユーザ又はgoto先のコードを欺くかどうかである。
何故ユーザを欺くか? 時には、ユーザはコードの存在を知りたくないからだ。例えば、多くのPerlコードは、呼出しのミスがあった所からのエラーを見せるためにCarpのcarp()とcroak()の両呼出しを使うが、そのコードが以下のようなものとするならば、
routine_that_calls_carp('bad','args');
コードの中のその行からエラーを得られる。だが、以下のようにどこかでオーバーライドまたはラップするならば(例えばデバッグ目的のために)、
my $orig = Their->can('routine_that_calls_carp');
local *Their::routine_that_calls_carp = sub {
warn "Called routine_that_calls_carp with ".join(', ', @_);
$orig->(@_);
};
エラーはオーバーライドの最後の行から報告され、それは正しいけれどもユーザには全く有難くない。しかし、’goto &sub’の支援があれば、これは簡単にフィックスされる。
my $orig = Their->can('routine_that_calls_carp');
local *Their::routine_that_calls_carp = sub {
warn "Called routine_that_calls_carp with ".join(', ', @_);
goto &$orig;
};
このバージョンであれば、一時しのぎルーチンはコールスタックから削除されるので、routine_that_calls_carpコードは元々のユーザのコードから直接呼ばれていると考え、エラーは正しく報告される。
さて、他のコードを欺くことについてはどうか? 再度、これは、実際に来た所から来たことを見せないために必要とする場合なのである。これの通常の理由は、import()メソッドの簡単なラップをすることであり、importメソッドは、誰が呼んだのか、よってどこへエキスポートすべきかを解決するためよくコールスタックを使用する。だから、
package My::Module;
use Their::Module;
は、
package My::Module;
BEGIN {
require Their::Module;
Their::Module->import;
}
と同等である。さて、Their::Moduleはone()、two()、three()関数をMy::Moduleの中でセットアップするが、My::Moduleを必要とし、かつ自家製のfour()をエキスポートしたいとする。以下のように書けるだろう。
package My::Wrapper::For::Their::Module;
sub import {
my $targ = caller;
{ no strict 'refs'; *{"${targ}::four"} = \&four; }
goto &Their::Module::import;
}
Their::Moduleは直接My::Moduleによって呼ばれると考えるだろう。従って、正しい所へエキスポートする。
勿論、これをするもっと良い普通の方法がある。Exporterベースのモジュールに対しては、export_to_levelを呼べる。例えば、Data::Dumper::Concise::SugarのショートカットDevel::Dwarnは以下を、
sub import {
Data::Dumper::Concise::Sugar->export_to_level(1, @_);
}
次の2つと同等なことを行うためにする。
use Data::Dumper::Concise::Sugar;
use Devel::Dwarn;
そして、問題のモジュールがSub::Exporterを使用しているならば、同様に’into’または’into_level’引数を他のモジュールのimportに渡せる。つまり、
Sub::Exporter::Using::Thing->import({ into => $targ }, @_);
又は、
Sub::Exporter::Using::Thing->import({ into_level => $targ }, @_);
は、そのようなimportルーチンをラップするように動く。
そして…この場合でさえ、通常は’goto &sub’を欲しく無い又は必要無いが、Exporter又はSub::Exporterを正しく使っていないエキスポータを持つモジュールが多くあり、デバッグのため大急ぎでする簡単な場合で、屡々goto変形を使うのが手っ取り早い。
要約すれば、’goto &sub’は悪魔ではなく、完全に役立たずではない。日常的に使うものでは絶対にないが、その機能を必要とする時、Perlの重要な能力である。
ああ、そうだ忘れてた。メリークリスマス。
MongoDBのPerlドライバのメンテナでもあるKristina Chodorow女史のブログを読みますと、いろいろとtwitterで中傷されるそうです。これらの中傷は主として反NoSQL派の人によるものですが、私はそもそもtwitterを止めればいいのにと思っています。twitterをするから余計な雑音を否応無く目にするわけで、只でさえ情報過多な現代社会において、連絡用でなければ、文字制限があるショートメッセージサービスでまともな議論なぞ出るはずが無いと思っています。
もっと言えば、NoSQLとSQLと言う分類もまた可笑しいのです。そもそも、RDBMSモデルの提唱者である故コッド博士は、SQLを死ぬまで反対し、失意の内に亡くなられました。博士はもっと自然言語に近い、本当にクエリし易いものを望んでいたのですが、ボラクルのおっさんが博士のアイデアを適当に曲げて、SQLの原型を作り製品化しました。それに引きずられて、多くの会社や人々は追随して行きました。ですから、率直に見て、SQLを自然だと思う人(いわゆるデータベース屋を除いて)がいるでしょうか。一般人のみならず、デベロッパーの下辺にいる私でもSQLを不自然の極みだと思います。
話を戻して、結局はRDBMSモデルからの脱却をするかどうかで分類されるべきなんです。個人的には、RDBMSモデルがアジャイルな時代には不向きであること(完全に捨てろとは言いません。ほぼ長年データ定義が変わらないシステムはそのまま利用すればいいのです。例えば会計システム等は最たるものでしょう)、またRDBMSモデルを捨てない限り、本来別物であった不自然の極みであるSQLの呪縛から逃れる術が無いだろうという二つの理由で、いわゆるドキュメント指向データベースの一つであるMongoDBに関心を持って来ました。そして、Kristina女史には言われのない中傷に負けないでと勝手に願っていたところへ、MongoDBの威力を示すエッセイ”Mongo Just Pawn in Game of Life”を読み、安心しました。以下にその私訳を載せておきます。
追記:
知人よりわざわざ御注進があり、”Mongo just pawn in game of life”なるセリフのある映画だかドラマだかあって、Mongo は登場人物の名前か何かではないか、と。
知ってます。勿論、見たこともありませんが、Kristina女史がこのドラマにかけていることは明らかなので、どういう訳にしようかと頭を捻ったところ、女史の真意をそのままに日本語化したのです。よくチェスは「人生ゲーム」に喩えられますから、そのまま表に出して訳するのが自然だと思いました。日本人が見ないドラマそのまま出しても仕方が無いですから。それにしても、私の身辺だけかも知れませんが、こちらの意を汲めない人が増えましたね。
Mongo、まさにチェスにおけるポーンだよ
2009年12月24日 Kristina Chodorow
これは、
nifty blog post on storing a chess board in MySQLとsnarky Tweet about NoSQL DBs(私は決して中傷しない)に対する回答である。
一方では、私がこれをするなんて信じられない。何のデータベースがチェスボードを格納出来ないのか? 他方では、面白いな、そして一度タイトルを考えたら、私は本当にそのポストを書かなければならなかった。要領を得ないデータストレージを開始しょう!
さて、よろしい、先ずはチェスピースの表現が必要だ。私は単純にUTF-8のシンボルとポジションを使いたいが、クエリのために、作業療法士が人間の読める文字列を持つことはいいことであろう。それゆえ、次のようなものを持とう。
{
"name" : "black king",
"symbol" : "♚",
"pos" : [8, "e"]
}
はは! 整数または文字列型のサブフィールドのまたサブフィールドに対して、リレーショナルデータベースはクエリ出来るか? 実際には出来るかも知れないが、私には分からない。兎も角も、右に沿って動かすと…
ところで、MongoDBは単にJavaScriptを走らせることが出来、私は必要なことをすべてするJavaScriptファイルを書く。以下に、基本的なチェスボードを作るコードがある。”db”は、接続しているデータベースであるグローバル変数だ。そのデフォルトは”test”であるので、”chess”データベースへ切替ることから始めよう。”chess”データベースが存在しなくても、何かをすれば作られるのだ。それから、それを展開する。
// use the "chess" database, creates it if it doesn't exist
db = db.getSisterDB("chess");
// make sure the db is empty (in case we run this multiple times)
db.dropDatabase();
// map indexes to chess board locations
column_map = {0 : "a", 1 : "b", 2 : "c", 3 : "d", 4 : "e", 5 : "f", 6 : "g", 7 : "h"};
// starting at 1a
color_char = {"black" : "█", "white" : " "};
color = "black";
for (i=1; i<=8; i++) {
for(j=0; j<8; j++) {
db.board.insert({x : i, y : column_map[j], color : color_char[color]})
/*
* switch the color of the square... it's always the opposite
* of the previous one, unless we're at the end of a row
*/
if (j != 7) {
color = color == "white" ? "black" : "white";
}
}
}
よろしい、今やピースをイテレーションし、それらのオブジェクトを作り、ボードに加えよう。
// create unique ids from symbols
function get_name(symbol, column) {
switch (symbol) {
case '♖':
case '♜':
return " rook " + (column < 4 ? "left" : "right");
case '♘':
case '♞':
return " knight " + (column < 4 ? "left" : "right");
case '♗':
case '♝':
return " bishop " + (column < 4 ? "left" : "right");
case '♕':
case '♛':
return " queen";
case '♔':
case '♚':
return " king";
case '♙':
case '♟':
return " pawn " + column;
}
}
// go through the 2D array of pieces, create the objs, and insert them
function add_pieces(color, color_str) {
for (row=0; row<color.length; row++) {
chess_row = row + (color_str == "white" ? 1 : 7);
for (column=0; column < color[row].length; column++) {
chess_column = column_map[column];
db.board.update({x : chess_row, y : chess_column}, {$set : {piece : {name : color_str+get_name(color[row][column], column), symbol : color[row][column], pos : [chess_row, chess_column]}}});
}
}
}
add_pieces([['♖','♘','♗','♔','♕','♗','♘','♖'], ['♙','♙','♙','♙','♙','♙','♙','♙']], "white");
add_pieces([['♟','♟','♟','♟','♟','♟','♟','♟'], ['♜','♞','♝','♚','♛','♝','♞','♜']], "black");
ちぇっ! 難しい部分は終わった。この乳児をプリントアウトしよう!
// sort by x ascending and y ascending
cursor = db.board.find().sort({x:1, y:1});
count = 0;
board = "";
while(cursor.hasNext()) {
square = cursor.next();
if (square.piece) {
board += square.piece.symbol;
}
else {
board += square.color;
}
count++;
if (count % 8 == 0) {
board += "\n";
}
}
print(board);
以下が結果である。
♖♘♗♔♕♗♘♖
♙♙♙♙♙♙♙♙
█ █ █ █
█ █ █ █
█ █ █ █
█ █ █ █
♟♟♟♟♟♟♟♟
♜♞♝♚♛♝♞♜
大変かっこいい。今や、シンボル、人に親しい名前、ボードポジションによってクエリ出来る。また、ピースを動かすためには二ヶ所修正するのみ。コピーとペーストをしたくないなら、私はchess.jsを付随している。
珍しく、Matt S. Trout氏が禁止語の一つも無く、素晴らしいエッセイ”Choosing a library”を書いています。もしかして誰かに言われたのかも知れませんが、本当に珍しいです。禁止語が一つでもあれば、私は正直言ってオタオタします。折角いいエッセイなのに、禁止語一つのために紹介しなかったことも多いのです。
このエッセイはライブラリを選ぶコツが主題ですが、Matt氏ほどの鬼才でも車輪の再発明に関して「3つの大罪」のうち2つまでやってしまったと考える件では、私は正直言って戦慄を覚えました。CPAN上に何でも(大した特徴も品質も無いのに)上げりゃいいという風潮に対する皮肉ではなかろうかと疑ったほどです。
以下に私訳を載せておきます。
ライブラリを選ぶ
2009年12月18日 Matt S. Trout
重要度によるライブラリ選択プロセス
よろしい、では、貴方はXをしたいと分かっているとしよう。そして、貴方は車輪の再発明したくないので、頑張ってライブラリを探す時だ。
勿論、最初の停留所はsearch.cpan.orgであり、そこで貴方はすぐに半ダースの候補を見つけるだろう。私にとっての第二停留所は、幾つかの関連IRCチャネルに質問することである。貴方の好みのオプションがtwitterでもメーリングリストでも何でも構わないが、実践家に意見を求め、彼等が何かを推薦出来るかを見るべきである。
進む前に、評価の判定の時なのだ。すなわち、このコードベースになろうとしているライブラリが如何に重要か? 一つのモジュールの中で一度だけ使われるものなのか? これは、特殊な目的のユーティリティモジュールに対してまずまず普通であり、そのライブラリに「動いている限り」なレベルの興味を示す。私はこれを「一度限り」ライブラリと呼ぶことにする。
次に高いレベルは、頻繁に必要とする目的のために使用されるであろうライブラリである。つまり、プロジェクトのための標準的なツールボックスの一部分となるものだ。私はこれを「ツールボックス」ライブラリと呼ぶことにする。
最後に上記の他、アプリケーションの大きな部分を定めるコードを持つ。これは必ずしもフレームワークを意味しないが、巨大なライブラリ、例えば貴方がデータベースを支援するアプリケーションを書いているのであれば、貴方のORMが多分そのカテゴリに入るであろう。貴方がメールクライアントを書いているのであれば、CPAN上で貴方が選んだMail::及びEmail::名前空間のものが、そうだろう。私はこれを「プラットフォーム」ライブラリと呼ぶことにする。
用語が定義され、候補が選ばれた。何をしようとしているのか?
最初の段階は、そのライブラリのメインドキュメントをざっと読むことだ。すなわち、「私が必要することをしてくれるらしいか?」と自問し、その答えがはっきりイエスで、これが「一度限り」クラスライブラリなら、この時点で私はインストールし、ちゃんと動くか試すだろう。2,3の目的のために使っているだけならば、私が気を付けることは、それらの目的に動くかどうかである。その状況でテストが失敗するならば、更なる注意をすぐ後に述べる。
しかし、「ツールボックス」ライブラリに対しては、それ以上にもっと注意しなければならない。必須事項でただ動くだけではなく、後にプロジェクトでおそらく必要となる事項についても知りたいのだ。だから、今少し考える時である。
候補のうちで、あるものは手続き型であり、あるものはOOである。あるものは簡単であり、あるものはフレキシブルである。必要としている用途が何のレベルであるか考える必要がある。しようとしていることが付随的又は平易的であれば、この領域で相当に複雑なことをするよりも、理想的なライブラリはいろいろ違って見える(再度繰り返すが、始めからアプリケーションのコアな目的に移るならば、それを「プラットフォーム」へ再分類したほうがいい)。
2,3の候補に絞れば、次の段階は、残る選択のためにテスト一式を割って入ることだ。考えている用途の類の例を探したい。その結果、作者がどのように、そのコードを使っているか感触を得る。勿論テスト一式は学習するには著しく歪んだレンズであるが、それでも普通は入手可能でベストなものである。テストの「感触」が良く、ドキュメントが、現在期待していることをするのみならず、近い将来に必要となることをするらしいと示唆しているのなら、「ツールボックス」ライブラリとしての使用をここで止め、プロトタイプを得られる。
しかし、「プラットフォーム」クラスライブラリとしては、これでもまだ不十分であると私は言いたい。一つのプロジェクトに、それ程重要で、それ程中心的であるかは、コードする時に手袋のようにフイットしなければならないだけではなく、貴方の考えと設計者の考えが異なる時、曲げる必要があるのだ。
残念ながら、これがかくも多くの人の、完璧な素晴らしいフレームワークの再発明に膨大な時間を浪費する理由だ。つまり、意見が違って、その答が意見にフィットする新しいものを造ることだったからである。そして、それが時には明白にベストな考えであるのだ。しかし、殆がそうはならない。
一般的に、すべき最も良いことは、貴方が依存する積りの本質的な事項のソースコードに割って入って、読み始めることだ。その姿かたちに対する感触を得よ。どのように動いているのか、どのようにして部品が纏まって全体となっているか、どのような味わいなのか、感触を得よ。ここはさっと読みなさい。実装の詳細を理解しようとしてはならない。ロジックの干満と大きなスケールでの信頼性を理解しょう。
それから、自問する。変てこなXをしなければならないとすれば、私に出来るだろうか? そして、もっと熱心に読み始める。三つの結論のうちの一つに到達するはずだ。つまり、(1)「オプションのこの組合せを渡せば、うむ…まさしく動くはずだ」、(2)このメソッドをここでオーバーライドすれば、動くはずだ」、(3)「コードのこのセクションを置換えれば、まだ動くだろう、かろうじて」。貴方が少なくとも(3)に行かなければ、おそらく貴方はこのライブラリを取りたくないだろう。(1)が理想だが、ライブラリを暫らく使ってみるまで、そんな場合を見抜くことは殆無いことに注意せよ。半年のDBIx::Classユーザがよく、「うむ、X、Y、Zを結果集合に渡せば…くっそ、動いた!」となる。だが、一ヶ月の使用経験の誰かはただ腕を振り回して、手際の悪いことをするだろう(「プラットフォーム」レベルのライブラリのトラブルは、険悪にもライブラリの近づきやすさを難しくする)。私は通常(2)の場合を期待し、それが分かれば幸せだ。
しかし、(3)の場合を見ることも実際には本当に重要である。つまり、ライブラリの一部分の広範囲な置換えを発見することは、貴方が一部分を広範囲に置換え出来ること示しているからである。Catalystは特にこのために、いいのである。すなわち、私はかって退屈な夕方、エンジン部のコードを引抜き、完全に違うものに置換え、それがIRCボットとなった…
貴方のやりたい変てこなことを通して、思考実験が最大限可能なことを思いつくとするならば、貴方はこの作業を出来るはずである。又は、少なくても文字通りスクラッチから構築するよりは、貴方が厳然たる優位を得ることに働くはずである。適正に注意して貴方のコードを分離するならば、プロジェクトの他の何よりも「プラットフォーム」レベルのライブラリがますます交換が難しくなる一方で、まだずっと可能性があるはずだ。
2つの最後の注意
第一に、同一のライブラリがプロジェクトに応じて、異なるレベルであってもよろしい。例えば、私が最初にMooseに出会った時、メタプロトコルのために欲しかったし、可能な限りのメタパワーの最大有利性を作るために、私はいじくり、手を加え、サブクラス化し、そこらじゅうに壊して再構築するであろうことを分かっていたので、「プラットフォーム」レベルと認定した。Mooseを「クラスビルダー」として使う平均的なプロジェクトでは、ほぼ確実に「ツールボックス」レベルに認定しなければならない。しかも、Dave Rolskyのマニュアル作業のおかげで、ドキュメントは十分に素晴らしく、貴方はおそらくテストする必要もない。
第二に、自家製を書くよりも、既存の「プラットフォーム」レベルのライブラリを曲げる方がいいことを理解するのは、通常困難な方法で学ぶ教訓である。私はずっと昔、自家製のテンプレートエンジンを書いたが、Template Toolkitを発見し、後悔した(私の目的のためには、Template Toolkitは「プラットフォーム」よりは、ほぼ間違いなく「ツールボックス」であったけれども)。私は自家製ウェブフレームワークを2回書いたが、Catalystを発見し、ソースを読む大変幸せな12時間を費やした。喜んで社内の所持品に「保守だけだ」とマークし、立ち去った。
野心に燃えるPerlプログラマの通常やってはいけない3つの大罪のうち2つをやってしまったと、その当時考えたことを憶えている…だが、私がテンプレートシステムとウェブフレームワークを作るのに無駄な時間を費やした一方で、ORMを書いていないので、少なくとも3番目の大罪を避けたようだ…。
CPANを常日頃観察している方は、やたらとプラグマもどきが多いことにお気づきだと思います。これらは目的が多少違っていても、多くはベストプラクティスのためだと言ってもいいでしょう。
いいプログラマの素質にはいろいろな要素があります。当然頭が切れる、理解が速い、構想力に秀でている等いろいろあるでしょう。私が思うには、最低限必要なことがあります。つまり己を知っていることなんです。自分の弱点、短所、悪癖等を自覚していることです。その弱さを知っていれば、当然それをカバーするものはないかと探して、strict、warningsのコアプラグマはほぼ誰でも必須であることに気がつくはずです。自分が駄目だからそれらに頼らざるを得ないのです。後、使うべきプラグマは個人差があるでしょう。日本人なら日本語リテラルを書く機会が多いから、どうせ書くならUTF-8でやるのが面倒が無いのでutf8プラグマでしょう(これも極端な言い方をすれば、非西洋人であることの弱点とも言えます)。
ですから、大上段にベストプラクティスを初心者に勧めなくても、己を知っていれば探すはずなんです。初心者と言ってもいろいろなレベルがあり、他言語出身者であれば己を知っているはずなので独力でやるでしょうが、問題はずぶの初心者です。ですが、己を知り、嗅覚の良い人であればすぐに探せるでしょう。初心者が初心者たりうるのは自分を知らないことに尽きます。子供が子供たりうるのは自分を知らないことと同じで、若年層が現実を直視せず夢を追い求めるのも同様だと言えます。
前置きが非常に長くなりましたが、最近のプラグマもどきに対して余計なお世話だと言いたいのは私のみならず、David Wheeler氏も”Make the Pragmas Stop!”で言っています(私とはちょっと意見が違いますが)。Wheeler氏は、App-Infoの件で一テスターに過ぎない私に、パッチを持っているなら適用するよと自ら行動する人でフランクな印象を持っています。
私訳を以下に載せておきます。
プラグマをストップさせよ!
2009年12月15日 David Wheeler
私は最近Perlコミュニティの中で、幾つかの開発に従事して来た。そして、非常に私を苛立せている。今までの数年間、私は同じマジナイで始まるモジュールを書いて来た。
package My::Module;
use strict;
our $VERSION = '0.01';
非常に簡単だ。モジュールの名前とバージョンを宣言し、私が何らアホなことを仕出かしていないことを確認する拘束をオンにしている。もっと最近では、ベストプラクティスの通りに、use warnings;を加えている。更にもっと最近では、私はコードをUTF-8で書きたいので、use utf8;も加え始めている。そして、私はPerl 5.10のフィーチュアのすべてをオンするのを好む。すべてのモジュールの始まりに同じマジナイを持つことは少し腹ただしいが、まだ容認出来る。
package My::Module;
use strict;
use warnings;
use feature ':5.10';
use utf8;
our $VERSION = '0.01';
とは言っても今までだが。昨年、chromaticが彼のModern::Perlモジュールと共に大層なことを始めた。モジュールの始めに一つだけの宣言を強制することにより、初心者がPerlを始める手助けをする親切なアイデアだった。
package My::Module;
use Modern::Perl;
our $VERSION = '0.01';
ああっ、実際には私向きに設計されていなかったが、大部分の深くないユーザにとっては、使用しなければならないプラグマを考える必要が無い。utf8プラグマを含んでいないという事実もまた、見込みが無いと私に思わせた。ならば、どうして?最近誰かがutf8プラグマに問題があると示唆した(今のところ、私はPerl Monksスレッドで見つけられない)。また別人もencodingプラグマに問題があるとレポートしている。そうならば、私のプログラムとそのインプットですべてがUTF8であると見なすことについてするべき正しいことは何なのか(私の言っていることが的はずれでなければ)? 私は全く分からない。
それのみならず、Modern::Perlは、ベストプラクティスを保証するプラグマ風モジュールのCPAN上における氾濫を引き起こしている。utf8をロードするが、strict、warnings、featureの幾つかのフィーチュアのみのcommon::senseがある。uni::perlは殆同じだ。自家製プラグマ風ローダーモジュールを書くことを許す、Damian ConwayのToolkitもある。Acme::Very::Modern::Perlすらあり、それはジョークを意味するが、本当にそうだろうか?
すべてのファイルの先頭にあるマジナイを私が簡単にしたいなら、何をするだろう?
今やもっと悪化している。featureに加えて、Perl 5.11はlegacyプラグマを導入し、legacyは古いPerlの挙動に戻ることを許す。例えば、古いユニコードセマンティクスに戻るためには、use legacy ‘unicode8bit’;としなければならない。これは驚いた、一体何なの?
私は主張する。プラグマの氾濫をストップさせよ! Perlの任意のバージョンがリリースされた時点でよく知られているベストプラクティスが、私が以下のように書いて、自動的にインポートされるようにせよ。
package My::Module '0.01';
use 5.12;
まさにそのもの。それ以外に何もない。5.12がリリースされた時点でベストプラクティスと考えられるものは何でも単純に使用される。5.14でベストプラクティスが変わるなら、use 5.14;に切り替えることが出来、そしてそれを受け入れるか、又はuse 5.12をそのままにし、5.12でベストプラクティスであったものを持続する。将来を見込むことはいいことなのだ。
ベストプラクティスは何であるべきなのか? 私のリストは以下を含むだろう。
・strict
・warnings
・features(それらのすべて)
・UTF-8(ソースコードと同様、スコープに対するインプットとアウトプットすべて)
貴方はそのリストに反対かも知れない。私がPerl 5ポーターの定めることに反対するかも知れない。が、その時は、貴方と私は何が含まれているのか分かり、プラグマをただ足すのか削除するのかも同じ必要性で出来るのである。しかしながら、全初心者用も含めて、確かに長期間に渡って含まれるべき最もな候補のリストはある。
個人的な通信で、chromaticは私に、Modern::Perlについて「熟練したPerlプログラマは望みどおりのことを得るための正しいマジナイを知っている。初心者はそうではない。多くの行のマジナイをせずとも、初心者にうまいデフォルトを与えられると私は思う」と言う。私は初心者に関しては賛成だが、熟練者に関しては賛成しない。私は凡そ15年間、Perlをハックして来たが、自分のモジュール内にベストな何のマジナイを使うべきか最早いい考えを持っていない。初心者を助け、同時に経験あるハッカーにも使用可能なパワーツールを作り、どうか経験あるハッカーを安心もさせてほしい。
Perl 5ポーター達が目指しているのはPerlの特定バージョンの宣言であると、私は考える。それが最もらしいプラグマのすべての操作も含み、私がする必要がないことを願いたい。
このページのすべての商標と著作権はそれぞれの所有者が有します。
コメントやユーザ日記に関しては投稿者が有します。
のこりのものは、© 2001-2010 OSDN です。