[翻訳]なぜそんなに確信が持てるのか?

前書き

この記事はC++標準化委員会の2019年12月公開の論文の1つ、Bjarne Stroustrupさんが書かれた「P1962R0 How can you be so certain?」という論文の和訳です。

この文章はC++標準化委員会における機能追加時の議論を念頭において、C++標準化委員会メンバーに向けて書かれています。したがって、読むにあたってはC++の機能などについてある程度知っている必要があるかと思います。

私の英語力はひよこ以下なので訳の正確性には全く保証がありません。特に、細部のニュアンスの解釈は大いに間違っている可能性があります(修正してやる!という方がいましたら、この記事のMarkdownソースからお願いします)。

以下の方に修正を賜りました

なお、翻訳の公開についてBjarne Stroustrupさんに連絡を取った所、問題ないとのお返事をいただいております。

以下本文

How can you be so certain? - Bjarne Stroustrup

私はJohn McPheeの本を読んでいてこの引用を見つけた。

「これが真実だ」ではなく「だから私には、私が今見ていると思うものを見ているように思う」と言おう。
--- 世界最高の地質学者の一人であるDavid Loveの言葉より

少し複雑かもしれないが、考えさせられた。私はよく、一部の人々がどのように彼らが示すほどの確信を得ているのか不思議に思っていたためだ。

我々はもっと良く考える必要がある

言語(およびライブラリ)拡張に関しては、根本的な問題の解決よりも技術的な詳細を詰める(どのキーワードが最も害が小さいかや、機能がどのように実装されるべきかを決定するなど)のに多くの時間を費やしているようだ。

確かに、我々は委員会初期の頃よりもはるかに優れた技術者集団となったが、大きな変更に関する議論がやや表面的なものになってはいないかと心配している。
私はときどき、査読済み学術論文 – それら論文の少なくとも一部は実証的な観察に基づいている – の厳密性が恋しくなる。私は度々、ある機能や拡張についての利点と欠点を慎重な計量が欠けているのを見る。
私は度々、我々のエビデンスへの計量が十分に徹底されており一貫しているかどうか、さらに言えば完全な言語と標準ライブラリにまたがる懸念にそもそも注意を払ったのかどうか、疑問に思うことがある。

  • 「そう思う」は技術的な根拠ではない
  • 「私の会社ならやる」は決定的な根拠ではない
  • 「私はどうしてもそれを必要としている」は決定的な根拠ではない
  • 「他のモダンな言語にはそれがある」は決定的な根拠ではない
  • 「我々はそれを実装することができる」は必要な要件ではあるが、機能追加のための十分な理由ではない
  • 「その部屋のほとんどの人はそれを気に入っていた」は十分な理由ではない

最後のポイントは反民主主義的に思えるかもしれないがそうではない。
部屋とはどの部屋であるのか?そこにいた人々はC++標準化委員会メンバーの代表だったのか?あるいはC++コミュニティの代表?彼らは何を好んだのか?それを好んだ理由は何だったのか?誰も反対しなかったのか?(反対した人がいたとしたら)その人はなぜ反対したのか?
(そのような)部屋にいる人々は概して自分の意思でそこにおり、通常、拡張を支持する人々は現状維持を支持する人々よりもはるかに雄弁でやる気に満ち溢れている。

実際のところ、単純に票を合計して3:1の得票差があるかを見るのは間違っていると思う。

コンセンサスを得るにはこのような(できればこれ以上の)得票差が必要であるが、それだけでは十分ではない。特に、部屋に十分に人数がおらず、そこにいる人々は週末で疲れている状態であり、主要なメンバーやベテランのメンバーが他の場所にいる場合などは十分ではない。誰が何に対して強く反対しているのかを検討することには常に価値がある。

強い支持者も強い反対者も必ずしも正しいとは限らない。時には強い感情が論理的な根拠の欠如を覆い隠してしまうこともある。
我々は今後何十年も使用される言語を定義している、もう少し謙虚さが必要だ。

あの頃を覚えているだろうか?

  • 全ての関数をメンバ関数にすることが一般的だった頃
  • 仮想関数はクールだったため、全ての関数は仮想化されるべきだと多くの人が主張していた頃
  • publicメンバは時代遅れだったため、全てのデータメンバは隠蔽されるべきだと多くの人が主張していた頃
  • ガベージコレクタは不可欠だと思われていた頃

私は今日のC++標準化委員会を構成する委員たちならこれらのような流行に乗ってしまったのではないかと疑っている。

今日、我々は多くの流行に囲まれているが、その中で何が長期的に有用で何が「単に流行っているだけ」なのかを判断するのは依然として困難である。そして、現在流行しているものに心を奪われるのは簡単なことだ。
どの問題に解決する価値があり、それはどれほど流行に左右されるのか。

裁決を遅くすべきだとか早くすべきだとか主張しているわけではない。例えば、コンセプトは遅すぎたし、<=>(一貫比較)は早すぎたと思っている。

私は、我々がより組織的で慎重で一貫した理由付けを行うことを提案する。

提案

解決策を提案するよりも問題を指摘する方が簡単だろう。

瓶に詰められるような設計の「魔法の源」はなく、実際に使用されるための言語設計(これは我々が議論していること)は単純な予測可能プロセスではない。
「問題」とは何か、どの問題に対処が必要か、多くの解決策のうちどれが最善なのか、についての完全な合意を得ることはできない。

ただし、それら対応策毎に過去の対応において上手く行ったことや、パッチの上にパッチを必要とするような長引く問題の原因などについてを学ばなければならない。
異なる提案にはそれぞれ異なる種類、異なる量の仕事が必要となる。

少なくとも議論・検討の初期段階では使用パターンとインターフェースに目を向けるべきであり、実装詳細にはあまり目を向けないでおくことを提案する。

ユーザーインターフェースと使用モデルがクリーンであるならば、実装は何年(何十年)かかけて改善される傾向にある。「どのように(実装される)?」よりも、「何が?」「なぜ?」(必要か)に集中する必要がある。
現在の技術の下では、最適なパフォーマンスよりも安定したインターフェースの方が重要である。標準は何十年にも渡って安定であることが要求される点で、多くのプロダクトと異なるためだ。

最近のC++標準化委員会での議論はきめ細かい制御によりプログラマーに「どのように?」をかなり具体的にさせることに集中しすぎており、そのため、使用パターンを進化させ実装を改善するということを難しくさせているように感じている。

確かに、きめ細かい制御と高レベルで一般的なインターフェースとの選択は意思決定プロセスの問題ではなく設計の問題であるが、一般的なインターフェースを選択することは、実装の議論ですべての実装詳細を突き詰めるのではなく限定しない選択肢を決めて済ませることを可能にし、標準化をシンプルにできる。

他の機能から完全に分離された言語(ライブラリ)機能は存在せず(少なくとも現在は存在していないはず)、そのような機能間の相互作用は最も難しい問題の一つであるが、多くの場合は問題としても有用なものとしても過小評価されている。
我々は、そのような他の言語機能や標準ライブラリコンポーネントとの相互作用を常に考慮しなければならない。特に、他の機能が開発中である場合これはとても難しい。

このことは、C++に大きな改善が施された後、常に小さなクリーンアップと小さなサポート機能の追加が必要になる理由の一つである。大規模な機能の導入から結果として得られる教訓を初めに予想することは出来ない。可能なあらゆるニーズ(初めは予想されていないような)に対応するために、機能はあまり精巧なものにしてはならない。
(そうてしまおうとすれば)まず、できない。次に、やろうとすれば、肥大化を生じ、我々はそれと永遠に付き合わなければならない。

C++標準化委員会における「委員会による設計(より正確には委員の連合による設計)」プロセスでは一般に、表明されたすべてのニーズを包含するような設計に落ち着く傾向にあり、そのように設計された承認当初の機能は酷いもので、肥大化している。表明されたすべてのニーズが多くのC++プログラマにとって(直接・間接的にも)現実的で重要というわけではない。

我々は、最小限の機能からスタートしてフィードバックに基づいて機能を成長させていくべきだ。
「最小限の機能」には、何が基本的で、何がクリーンで、何が不可欠であるのかに焦点を当てる必要がある。 オリジナルのUnixのことを考えてみてほしい(そして、現代の派生と比較してみてほしい)。

ある機能の、最初の最小限のコアから成熟したファシリティへの成長とは、一般化と他の言語・ライブラリの(すでに成長を終えた)ファシリティとの統合でなければならない。間違っても、パッチの上にパッチを重ね続けるような特殊ケースを追加するものであってはならない。
そのようなパッチをしなければならない特殊ケースが発生したとしたら、初期設計に欠陥があったということだ。例えば、(C++標準完成間近のような)土壇場で行われる機能の「改善」などは気がかりだ。

設計において回避不能な不確実性にアプローチするために2つの基本的な方法がある

  • 誰もが役に立つと感じるまで「改善」し続ける
  • 原則的で基本的なものだけが残るまで機能を削ぎ落とす

どちらを選んだとしても、これらのアプローチの後で得られた経験により変更(プレリリース)と追加(リリース後にできるすべてのこと)の必要性が明らかになるだろう。私は疑いもなく2番目のアプローチを支持しており、これこそが原則とフィードバックに基づいた適切なエンジニアリングであると考えている。私は前者をハッキングと政治と考える。

標準化のプロセスには確かに妥協が必要だが、そうした妥協が単に合意が取れなかっただけであるとか機能を肥大化させただけ、となることが無いように保証しなければならない。
ある問題について検討するときは、我々は常に「誰が利益を得るか?」「どのように利益をもたらすか?」「その利益にはどれほど意義があるのか?」を明確にするようにしなければならない。例えばP1700R0 Audience Tablesのように。
また、「誰が新しい問題に苦しむことになるのか?」も明確かつ具体的であること。「平均的なユーザーは〜できない」という主張はどちらも欠けている。

提出された提案の利点は誰にとっても自明でもなければ明白でも無い。その問いに対する最初の回答を用意するのは提案者の仕事である(すべての提案にはコストがかかっていることに注意されたい。委員会の時間、実装、文書化、教育、古い実装や文書の扱いなど)。

通常、目標の設定は機能の詳細設計と実装よりもはるかに困難だ。我々は優れた技術者であり、一度目標が設定されれば必要な作業を行うための理論と経験を持ち合わせている。しかし残念なことに、我々はその目標を明確にしその合意を得ることが得意では無い。多くの場合、そのような合意は複雑な要件に帰着してしまう。

言語設計は製品開発では無く、我々には基本的な優先事項を決定するより高いレベルの管理者が存在していない。所属する会社がそのような事業を行なっている訳でも無い限り、我々の中でこのような(言語設計のような)職場経験を持つ人はほとんど居ない。ここ数年、そのような製品開発のアナロジーによって規格化を進めることが度を越しているように思う。

根拠もデータもそれ自体では決定的では無い。我々は巧妙な根拠で自分を騙すことに長け過ぎていて、データには常に解釈が必要である。学術文献が得意なことの一つは実験によるデータの解釈だ。
我々の経験は必然的に狭く、不完全だ。C++の世界はあまりに広大で、誰もがその全てを知ることはできない。にも関わらず、問題とその解決のスタイルは時間とともに変化していく。

あなたの所属する会社や業界に対する認識は必ずしも正しく無いかもしれず、仮に正しかったとしてもC++コミュニティ全体にとっては決定的では無いかもしれない。 あなたのニーズを完全に満たそうとすることは、C++コミュニティ全体にとって害かもしれない。これは我々委員会のメンバー全員に当てはまる事だ。

いくつもの「完璧な」言語は失敗してきた、注意を怠ればC++もまた失敗するかもしれない。我々には柔軟さと責任感が必要だ。すなわち、我々の設計は世界が変化しても意味を持つようなものでなくてはならない(世界が変化した場合に、ではなく)。 重要な問題に正しく対処しそれが将来の機能改善を妨げないことを100%確約できないため、あらゆる設計にはリスクが伴うことを理解しなければならない。しかし、それが我々を麻痺させるものであってはならない。何もしないこともまた(良きにせよ悪しきにせよ)結果をもたらす。リスクを取ることは不可避であるが、それは意図的で考え抜かれたリスクにしなければならない。

完璧を主張していては進歩できない。機能することが分かっているものに基づいて慎重に初期設計を進め、あとから磨き上げる必要がある。これは、どこに行きたいかについてかなり明確な考えを持っている場合にのみ可能である。そのような大まかな展望が欠けていれば、拡張は単なるハッキングであり、パッチにパッチを重ねることになるだろう。現在稼働している大規模なシステムは、常にいくつかの小さなシステムの仕事の結果の集大成となっている。

設計と改善について完全に自由な選択肢はない。 「世の中」には数十億行のコードがあり、数百万の教科書や人気のブログがあり、多くの古い知識は数百万の頭の中に保存されている。 また、新旧のファシリティが円滑に相互運用されるためには、既存の言語機能と型システムを尊重する必要がある。

言語が安定であることは特徴であると同時に設計上の重大な制約でもある。古いコードや設計アプローチが「進歩」の邪魔になると常にイライラするが、人々はコードが壊れることを本当に嫌う。 多くのコードは非標準の機能、もしくはバグ(コード、コンパイラ、または標準による)と言ってよいほど曖昧な機能に依存しているため、ある程度の破損は避けられない。

私は多くの人の態度を要約できる

  • C++は複雑すぎる。もっと小さく、単純で、きれいにする必要がある。
  • そして、この2つの機能を追加してほしい。
  • そして、何をしたとしても私のコードを壊さないで!

私もそう思うが、もちろんこれは不可能だ。ユーザーは古いバージョンをサポートするコンパイラを要求するため、機能の非推奨化でさえ実際には機能しなかった。主要な機能を廃止することは不可能であり、小さな機能を廃止することは大した益のない面倒なことである。
20年前のC++コードは今日でも実行できる、これはC++を利用する大きな理由でもある。その理由の1つは、今日書いたコードは20年後にも動作するという期待を抱かせるためだ。

互換性は常に過敏な問題である。言語自体が保証できる範囲を超えてシンプルさと正しさを両立するために、コーディングガイドラインと静的解析に注力することをお勧めする。C++コアガイドラインはそのいい例だと思っている。

我々は型安全でリソース安全なC++を書くことができ、そうすべきだ。 言語の進化はこの理想を支えるものでなくてはならない(「The Design and Evolution of C++」や「 Direction for ISO C++」によって文書化されている)。
既存の巨大なコードベースに型安全性とリソース安全性を確保するための手法や新機能を適用するのは非常に困難だが、ひどく互換性のない複数の言語バージョンを扱うよりもはるかに管理しやすい。

通常、実装経験は設計の改善に有効だが、実装作業は設計を凍結する傾向にあるため、早期実装をした場合には代替案や改善は無視されるかもしれない。一般に、基本的要件と原則が明確に記述され(できれば書面で)、主要なユースケースが選択される前に実装してしまうのは賢明ではない。

使用経験の報告は最も価値があるが、取得することは困難であり大規模なものは不可能だ。通常、我々は自分達で選んだ、(通常その道を極めたC++愛好家の)小さなグループの経験で間に合わせなければならない。ただし、実装経験と同様に(単一の小さくまとまったグループのみが関与する場合は特に)初期の経験報告は疑ってかかる必要がある。

「あと2つの機能だけ」を望むのは委員会のメンバーだけではない。300人以上のメンバーで構成される委員会があり、メンバーは皆基本的にC++に入れたい機能を1つか2つは持っていて、多くのメンバーは更にいくつか持っている。 あまりに多くの機能を追加してしまえば「C++が沈む」という意見を私は変えていない(P0977R0 Remember the Vasa!)。実際P0977を書いて以降、新しい提案は洪水のように増加していると感じる。

我々はあまりにも急ぎすぎている。我々は多くのことをするか、もしくは少ないことを速くすることができる。両方を行いながら品質と一貫性を維持することはできはしない。我々はより抑制的で選択的にならなければならない。
全ての設計には長所、短所、および制限がある。可能性のある問題や代替案について真剣かつ誠実に議論しないまま設計を提示してはならない。可能性のある問題について調査するのは提案者の仕事の一つであり、「販売するだけの仕事(提案するだけ)」は知的に誠実ではない。

「対立陣営」の人々によって書かれたコルーチンに関する「賛否両方の立場からの論文」は非常に有益だった(Coroutines: Use-cases and Trade-offsCoroutines: Language and Implementation Impact)。
理想的な提案はいくつかの理論といくつかの実経験の両方を反映したものであり、関連する文献(多くの場合学術的なもの)の考慮もより慎重にならねばならない。主要な提案には常にいくつかの関連文献が記されている。

設計という作業には、様々な懸念事項と原則とのバランスを取ることが伴う。盲目的に従うことのできる絶対的で破ることのできない原則などは存在しない。これが、「The Design and Evolution of C++」において数十の「経験則」をリストアップしている理由だ。

ただ残念なことに、最終的には必ず「嗜好」になり、群衆にはそれがない。しかし、この文書には隠されたチェックリストが含まれているように見えるかもしれない – それもまた、「嗜好」によって解釈される。

モチベーション

この文書を書くきっかけとなった最近の議論には次のものがある

なぜあなたはそんなに確信が持てるのか?

誰かが私にやんわりと指摘するかもしれない:「あなたはしばしば強い主張を表明しており、他の人々があなたの意見を受け入れなかった時には腹を立てていることさえあった」と
もちろん、私は怒りを見せるべきではない。申し訳ない。
それが露になるとき、それは大抵、長年の仕事の後の焦りか、全ての人や議論が同じ基準に立っているわけではないという感覚の結果である。また、長年の仕事の後では、全ての新しい人々に対して寛大であることは困難だ。
怒りを見せないようにするだけでなく怒りを覚えないようにしているが、私は聖人ではないため、こうした問題をいつも気に掛けている。

我々が規格化のためにどのような話題を検討しているのか、私は100%把握してはいない。ただ、100%の把握が論理的に可能だと考えていないので、経験に基づく推測の必要性を受け入れる。これまでのところは、我々/C++はそれほどまずいことをしてはいないようだ。

私がかなりの確信を持つ時、私の信念は通常、何十年もの先公技術、理論、経験、および思想に基づいている。例えば、

  • コンストラクタとデストラクタの組み合わせとRAII
    多くの人にとってそれは1979年にどこからともなく現れたかのように見えていたが、それはOSでの経験に深いルーツを持っており、当時の言語ではこれらの確立された経験を直接表現できなかった私の無力感を反映していた。これはC++の基礎だと考えている。
  • コンセプト
    1980年代半ばに総称型とそのアルゴリズムの引数を指定する方法を検討し始めた。それ以前に、初期のC++で使用されていたマクロベースの手法はスケールしないことに気づき、10年以上に渡って文献を追っていた。
    現在の設計は2003年(当時公開された)およびそれ以前の仕事にルーツを持つ。その仕事には、実験、実装、学術出版、委員会での論文、実使用、教育、などが含まれていた。2015年ごろから後にはユーザ向けの大きな改善は見られなくなった(専門家/詳細実装者向けの改善とは対照的に)。
    コンセプトはテンプレートの設計を完了するために必要であり、ジェネリックプログラミングサポートに必須なものだと考えている。
  • 例外
    この問題をここで詳細に論じるにはあまりに炎上しているが、問題の原因は多数のエラー報告メカニズムにある。議論と研究は少なくとも1974年まで遡る(例えば、P1947R0 C++ exceptions and alternativesを参照)。
  • 契約プログラミング
    様々な形式の不変条件の使用は1970年代半ばのPeter Naur’sの仕事に遡り、多くの現代の思想はBertrand MeyerによるEiffelでの仕事を反映している(それを真似ることとそのいくつかの面に反対することの両方において(例えば、catcallやクラス不変条件))。他にも、アサーションとインターフェース指定によってサポートされる静的解析の仕事にもルーツを持つ。
    一方、「継続」に関する仕事は比較的新しいもので、ほとんどがブルームバーグにおける論証と経験に基づいている。
    C++20での提案が失敗する前にも、2つの試みが失敗していたことに注意されたい。その理由を考えなければならない。
  • ドット演算子
    私のC++においての基本的な目標の一つは(1979年当初からの)、ユーザー定義型と組み込み型の両方を適切にサポートすることだ。例えば、あらゆる面で組み込みのintと同等の整数型を定義可能にしたい(コンパイル時間は例外として)。しかし、publicメンバーを持つ型ではoperator.()がないことから、そのような型の制御方法とアクセス方法に空白が残っている(例えば、シンプルで汎用的なスマート参照プロクシクラスを作成できない)。1980年の最初の拡張提案はoperator.()に関するものだった。
    C++operator.()ないしは同等なもの無くしては完全ではない。
  • 統一的な関数呼び出し
    x.f(y)f(x, y)の表記上の差は、ある操作には常に単一の最重要オブジェクトが存在するというオブジェクト指向の欠陥概念に基づいている。私はその採用を失敗してしまった。当時の理解は浅かった(しかし、非常に流行していた)。それでも、sqrt(2)x + yをその考えが引き起こす問題の例として挙げた。
    ジェネリックプログラミングにおいては、x.f(y)f(x, y)の区別はライブラリ設計とその使用において問題となる(柔軟でない)。コンセプトによって、このような問題は形式化される。
    繰り返しになるが、問題と解決策は数十年も前から存在している。f(x, y, z)において仮装引数を許可すればマルチメソッドが得られる。

契約プログラミング以外のものは、C++がどうあるべきか?ということについての私の長期的な視点である。ただ、契約プログラミングにおける静的解析にまつわる部分はそこに属している。
私は契約プログラミングにおける実行時チェックを嫌ってはいないが、それを長期的な視点の一部として主張することは出来ない。例えば、「The Design and Evolution of C++」では契約プログラミングについて触れていないが、当時から契約プログラミングという物を把握していた。

結論

単純明快な結論を出すことはできない。優れた設計を保証するために誰もが従うことのできる単純なルールのセットが存在するとは思えないためだ。
この論文は知的謙虚さを駆り立てる偉大な経験主義者からの引用に動機付けられた。他の所では、彼は自然の観察によって立証されていない理論的・学術的な概念に対して警告していた。

ある意味ではそれが結論であろう:自分の思う事実やそれに基づく理論を過信してはならない。事実を調査しその事実に調和する理論を構築せよ。

謝辞

この文書の草案についてメーリングリストでの議論に参加してくれた全員に感謝する。