2009-08-14

Bjarne Stroustrup、Conceptと未来を語る

センスとフィーリングで大胆な意訳を試みた。Danny Kalevがやたらとウザい奴に成り下がっていたり、Bjarne Stroustrupがコミカルなまでにジジイ臭いのは仕様。ただ、DKの言い回しは、ちょっとやり過ぎたかも知れない。実際原文を読んでいると、これぐらいムカつく言い回しなのだが。

Page 1: Concepts: Disappointment Without Defeat

Danny Kalev

今回のconceptの失敗についてどう受け止めてますか。今回の事件をどう思いますか。これは、あるいは、将来的に新機能を提案する妨げになると思いますか。

Bjarne Stroustrup

C++0xにconceptを入れないと決定したことについてかね。ワシとしては、conceptが失敗したとは思っておらんよ。今回の問題は、個人的にはユーザビリティの問題だと思っておるんだが、まあ、せいぜい数週間もあれば、修正できたと考えておる。まあ、委員会は、それほど早く解決できないと考えておったようだな。皆、conceptのアイディア自体は気に入っておるんだがな。一度ドラフトから外されると、もう一度入れるには、また長い時間がかかると、あれほど言っておいたんだがな。「失敗」と「何百万人ものプログラマが使うのに十分な規格ではなかった」というのは、大きく違っていると言えるな

小石を町のこっちからあっちへ移動させるのは、砂粒を動かすのと同じぐらい、造作もないことだろう。しかしね、小石がどこへ移動しようが、誰も気になどしないが(靴の中にある場合は別だがね)、砂粒を動かすのが、時として非常に重要な場合もあるのだ。つまりは、現実に広く使われているC++を変更するのは難しいが、何百万人ものプログラマが恩恵を受けるかもしれんのだ。これこそワシが、大変で反発も多いものの、C++を改良しようとしている理由だ。

今回のことをどう思うかって。残念だね。だがめげたりはせんな。もっと酷いことになっていたかもしれんのだからね。例えば、問題のある「concept」を規格に入れてしまったかもしれん。

Danny Kalev

コンセプトの規格制定には、7年もかかってますよね。でも、STLの開発は、その半分程度の期間でしたよね。なんでconceptはこれほど難しかったのですか。

Bjarne Stroustrup

ワシは、conceptは2002年から始まったと考えておる。ワシが最初の設計のペーパーを発表したのは、2003年のことだ。Alex Stepanovが、完全なSTLの実装を、Andrew Koenigとワシに見せたのは、1994年のことだった。だが、2002年から2009年と、1994年から1998年とは、比べようがないな。Alexは少なくとも、1976年からSTLを開発しておったんだからな。

しかし、君の質問に答えてみるとだな。Conceptの規格を設計することは難しい。というのも、C++の型システムそのものだからだな。conceptの規格を正しく設計するというのは、C++の規格を、より明確にするということだ。C++標準ライブラリの規定も、より詳しく書かねばならん。言ってみればだね、conceptの規格を正しく設計するというのは、C++規格の不明確な部分を、整理するということだな。また、conceptの規格を正しく設計していれば、

  1. コンパイル時間が極端に増えたりなどせんし、
  2. 実行時のパフォーマンスが下がったりもせんし、
  3. ジェネリックプログラミングを制限することもないのだ。

ワシがフランクフルト前のペーパー(訳注:N2906:Simplifying the use of conceptsのことか)で心配したのは、最後の項目だな。みんな、最初ばかりを気にしておる。

思うに、みんな、conceptが何をするもので、どうあるべきかというのが、分かっておらんようだな。例で示そう。Conceptがあれば、違いの明らかなsort()を四種類書くことが出来る。

template<Container C>
  void sort(C& c); 
template<Container C, Comparator Cmp>
  void sort(C& c, Cmp cmp);
template<RandomAccessIterator Iter> 
  void sort(Iter p, Iter q); 
template<RandomAccessIterator Iter, Comparator Cmp>
  void sort(Iter p, Iter q, Cmp cmp);

大方の人には、違いが簡単に分かるだろう。そして、なぜC++98で規定しなかったのかと思うことだろう。sort()は一組のランダムアクセスイテレーターと、あるいは比較関数をも、引数に取る。だいたいみんな、もっと簡単な、sort()にコンテナをまるごと渡すようなことが、したいと思っておる。この例では、Conceptを使ってテンプレート引数に何が必要かを明示することによって、何が言いたいかを表現しているわけだな。その結果、オーバーロードできたり、テンプレート引数が間違っていた場合には、すぐにエラーメッセージが得られるというわけだ。たとえば、sort(lst.begin(),lst.end()) と書いておいて、lstが実はlistだった場合、C++98のエラーメッセージは遅れてリンク時になり、大抵は訳の分からんものになるのだ。Conceptがあれば、すぐにエラーがだせて、しかも正確だ。そうそう、型チェックとオーバーロードに必要なのは、宣言だけだ。普通の関数と同じで、定義(関数本体)は、どこにでも書けるというわけだ。

なぜこの実装が難しいのか。この例は簡単に説明、実装ができるが、テンプレートを書く際には、非常に微妙な型の詳細に依存しているのだ。例えば、テンプレート引数の型はtrivialなデストラクタがなければならんとか、テンプレート引数の型はmove-constructibleでなければならんとか、テンプレート引数の型はunionでなければならんとかだな。なぜなら、そのコードにおいては、そういうことが非常に重要だからだ。ワシの経験では、誰にも重要ではないC++の型システムというのは、存在せん。ある箇所で利用されるあらゆる可能性を、conceptで網羅するのは、非常に労力がかかる。そして、完璧にconceptで網羅しきれずに、問題になるのだ。

Conceptの利点は、テンプレートのユーザー(訳注:テンプレートコードを書くプログラマという意味ではなく、他人のすでに書いたテンプレートコードなライブラリを、ただ使うだけの、ユーザーとしてプログラマのこと)だけのものではない。テンプレートで実装する人(訳注:ユーザーが使うテンプレートなライブラリを書く、本物のプログラマのこと)にとっても重要なのだ。以下を考えてみ給え。

template<ForwardIterator Iter, class V>
  requires Comparable<Iter::value_type, V>
Iter find(Iter first, Iter last, V x)
{
  while (first!=last && *first!=x) first = first+1;
  return first;
}

これは、一見、正しそうに見える。実際、標準ライブラリのfind()は、一組のforward iteratorを取るし、値は、イテレーターのvalue_typeと比較可能でなければならない。これは、Conceptを用いて、標準ライブラリの規定を、簡潔に表現しているのだ。しかし、このコードの間違いは、forward iteratorは、+演算子を提供していないという所にある。++演算子だけだ。コンパイラは、上記のコードの、first = first+1の部分が間違っていることを、正確に指摘することが出来る。C++98だけでは、このエラーを見つけるには、テストか、頭のいいプログラマが必要になるのだ。

これについては、悪くは思わないだろう。もっとも、今回の論点は、もっと複雑で微妙な部分に関することなのだがね。

「エラーメッセージの良し悪しなんて、どうでもいいね」という人もおるかもしれん。まあ、どうでもいいわけがないのだが、これはconceptのほんのひとつの利点に過ぎん。もっと重要な利点とは、プログラミングスタイルとコード品質なのだ。関数の引数の宣言や引数の型チェックがない、C++以前のCの時代が想像できるかね。当時は、少なからぬ数の人が、

double sqrt(double);// モダンな関数宣言

上記を、下記の使い慣れた奴に比べて冗長すぎると抜かしていたんだ。

double sqrt();// 関数プロトタイプが無かった時代のCの関数宣言

コンパイル時間の増加を心配していた人もおった。実行時のオーバーヘッドを心配していた人もおった。そうだよ、本当にいたんだ。何しろ、intからdoubleへの予期しない型変換が発生するかもしれんのだからな。難しすぎることを心配していた人もおった。「果たして素人プログラマに関数宣言のような機能が使いこなせるだろうか?」とな。互換性も心配されていた。「コンパイル時及びリンク時に、古いコードとどうやって整合性を取ればいいのだ?」と。今となっては、どんな時代後れのCプログラマでも、「当時は、関数宣言すらなくて、一体どうやって、やっていけたんだろう」と疑うぐらいだ。

Danny Kalev

すると結論として、conceptがボロ負けしたのは、結局、一度に多くのことを修正しすぎたり、あるいは、C++をまるっきり新しい言語に仕立てようとしたからってことでしょうか? だいたい、C++は、templateにのめり込みすぎていて、ちょっと問題ありますしね。プログラマが読み書きするコードと、コンパイラが、テンプレートのインスタンス化の際に生成する、実際の、目に見えないコードとは、全然別物ですしね。言ってみれば、C++は現状維持した方がいいんじゃないでしょうか。templateは制限ありすぎですけど。

Bjarne Stroustrup

いや、ワシはConceptがボロ負けしたとは思っておらん。そもそもワシは、Conceptが多くのことを修正するだとか、C++を全然別の新言語に変えるものだとは考えておらん。Conceptはあるひとつの目的の為に導入されたのだ。ジェネリックプログラミングというテンプレートの高度な使い方を、直接言語側でサポートするためだ。現状では、コメントだとか、仕様書だとか、ドキュメントの中で記述しているようなことを、直接表現できるようにする為なのだ。よくデザインされたテンプレートコードというのは、引数の型にどのような操作をするのかを、きちんと考えて書かれているのだ。良いコードというのは、そういう必要条件という考えが、ドキュメント化されているものだ。標準規格のrequirements tableみたいな物だと考えてくれればいい。つまり、現在では、ほとんどのテンプレートコードは、暗黙のうちにConceptという考え方を使っているのだ。

なぜ言語で直接conceptという考え方をサポートする必要があるかと言えば、暗黙的な考えだけでは、十分ではないからだ。コンパイラは、コメントや、仕様書や、標準規格書や、プログラマ(当然私も含む)の脳内仕様を、読んでくれたりはしないのだ。つまり、言語としての、明示的なconceptのサポートがないというのは、バグや分かりづらいエラーメッセージの原因なのだ。テンプレート宣言におけるConceptと、関数宣言の引数(関数プロトタイプ)とは、皆が考える以上に、よく似ていると思う。

ワシは、テンプレートを「ちょっと問題あり」とは考えておらん。まず、これはマクロではない。コンパイラがテンプレートコードを変換して実行コードを生成するというのは、非テンプレートコードを置換するほど、心配するようなものではない。これを心配したり、テンプレートをマクロだと思う人がいるというのは、テンプレート引数の必要条件が、暗黙的だからだ。呼び出した時点でチェックされないのだ。そういう「変換」を心配するというのは神経質すぎる。逆に何も心配しないのなら、関数プロトタイプを使わずにCで書けばいい。

Page 2: Concept Awareness

Danny Kalev

Conceptがワーキングドラフト入りしてから数ヶ月後の、2000年3月に、Howard Hinnantが訊ねてましたよね。「素人プログラマにまでConceptを強要するリスクはどうだ。利点はあるのか」と。規格の設計段階でこういう意見が出されてないってのは驚きですよね。委員会には、もっとチェックやバランスが必要なんじゃないですかね。特に、Conceptみたいなドでかい新機能に対しては。

Bjarne stroustrup

委員会は完璧ではない。どんな団体だってそうだ。だがしかし、君の考えているよりはマシだよ。メンバーの何人かは、そういう質問を取り上げて、委員会を勝手だとか邪悪だとか言うだろうがな。

リスクを心配したり、リスクと利点を考慮したのは、何もHowardが始めてというわけではない。Conceptに携わってきた者全員が、第一日目から考えていた事だ。そもそもワシは、テンプレート規格の設計の第一日目から、テンプレート引数のチェックの利点欠点について考えていたのだ。1986年から考えていたのだ。D&Eを読み給え。 いまConceptが無い理由というのは、1988年当時は、柔軟性と汎用性、パフォーマンス、優れた早期チェックを実現するにはどうすればいいのか、分からなかったのだ。ワシの知る限り、当時は誰にも分からなかったのだ。結局ワシは、後期チェックと酷いエラーメッセージを犠牲にして、柔軟性と汎用性とパフォーマンスを重視したのだ。2006年には、ワシらには、問題の解決方法が分かっていた。この違いは大きい。2009年の問題は、一部の連中が、長年の研究と、実装とライブラリ設計の経験をもってしても、まだ「十分ではない」と考えていたことだ。つまり、Conceptの一部は、まだ標準入りするまでに、改良が必要であるということだ。特にワシとしては、Conceptの利用経験から得られた結果が、十分にConceptの規格の設計に反映されていないと思う。

Howardが、リスクと利点について質問したかどうか、ワシはよく覚えておらん。そういう質問をした人がおったことは確実だがね。実際の質問は、利用に関しての事だ。ワシの"simplifying the use of concepts"ペーパーによれば、

そのスレは、Howard Hinnantが、ライブラリの設計方針について、二通りの方法があることについて質問したことから始まった。ひとつは、エキスパートでもない多くの素人ユーザーもConcept mapを書く必要があるもので、もう一つは、あまりエレガントではないが、Concept mapと、そもそもconcept自体を使わないことで、素人ユーザーがconceptを理解する必要をなくすものだった。

すでにAlisdair Meredithが似たような質問をしておった。「auto conceptの使用は、レガシーコードのみに推奨されるべきだろうか」

ワシがその時も今現在も心配しておる事は、ユーザビリティだ。

委員会はISOのルールに従って運営されておる。堅苦しくて、保守的である。完全に公平である。誰でも、経験、教養、商業目的の如何を問わず参加でき、国単位での公平な投票が実施される。C++委員会のドキュメントはWeb上で公開されている。C++委員会はパイオニアなのだ。さらに、国際標準委員会と個々のメンバー達は、常にC++開発者と関わろうと努力しておる。ワシの出版物や、インタビューや、発言録は、そのほんの一部に過ぎん。

世界中で使われている言語を大幅に変えるのは、とても難しいことだ。それはある意味いいことで、他の独自(原語:proprietary)言語と比べて、C++の最大の利点とは、変化しにくいということだ。ワシは、これ以上、「チェックやバランス」が必要だとは思っておらん。強いて言えば、長期的な物の見方に囚われすぎていることぐらいか。

Danny Kalev

「チェックやバランス」についてもうちょっと具体的に言います。Boostという団体では、非公式の新ライブラリを、委員会が標準規格に入れる前に、実環境で実験でき、さらに多くのC++プログラマからフィードバックを得ることができます。しかし、委員会は、コア言語機能に対しては、似たような「実験場」を持っていません。つまりは、多くのコア言語機能は、まともな実経験やテストもなしに、ワーキングドラフト入りしているんじゃないですかね。Conceptはつまり、そういうことだったんじゃないですかね。コアC++実験団体というようなものはアリですか。役に立つと思いますか。

Bjarne stroustrup

conceptについての君の考察は間違っとるな。そういうことなら、ワシらはそういう「実験場」を持っておった。それはconceptGCCであり、主にインディアナやテキサスA&M大学でconceptを研究していた連中であり、主にBoostにおいてconceptをライブラリで使おうとしていた連中だ。conceptに対して心配していた事というのは、そうした研究が十分だったかということ、規格設計は十分練られていたかということ(ワシとしてはこれが一番心配だった)、もっと長い時間が必要で、規格制定を遅らせるのではないかということ(これが大半の人の心配していたこと)だ。

実際、すべての新機能は、なにがしかのコンパイラで実験されておったのだ。問題は、「規格設計を肯定するに足る実験はどの程度か」ということだ。機能毎に、答えは変わるだろうと思う。

主要なC++ユーザーやC++ベンダーから、年間数万ドルぐらいの支援金を募って、そういう「C++設計及び検証ラボ」なるものを設立、運営するのは、理にかなっておる。だが、だれも興味を持っておらんようだな。コモンズの悲劇によく似ているが、連中はむしろ、その100倍のカネを、なにがしかの製品につぎ込んで、また違った商業的利益を得たいようなのだ。

Danny Kalev

ていうか、よく分かんないんですけど、Conceptとrvalue referenceってデジャヴな気がするんですよね。当初、rvalue referenceは、大部分のユーザにも分かりやすいはずだったじゃないですか。でも今は違いますよね。素人プログラマも、move constructorとか、関数のオーバーローディングのルール変更とか、void A::f(); void A::f()&; and void A::f()&&;は全部違う意味だとか、rvalueはもはやlvalueにならないだとか、そういった事を知っていないといけないんですよ。rvalue referenceもConceptみたいに、複雑すぎて、不安定で、無駄にバカでかくなってるんじゃないですか。ホントに入れる価値あるんですか。

Bjarne stroustrup

もう一度言うが、「素人」というのはやめたほうがいい。まず図々しい。そもそも実際、ワシらは、外から見れば皆「素人」なのだ。現代のソフトウェアは、一人の人間の手に負えないほど複雑なので、皆素人に成り下がるのだ。ワシだって大抵の事に関して「素人」だし、君だってそうだ。ワシらが皆、大抵のことには「素人」なのだから、そういう言い方はやめて、さっさと勉強を始めないかね。「分割して攻略」(原語:分割統治 "Divide and conquer")が基本。abstractionとlayeringは役立つ方法だ。

すべての言語設計というのは、レイヤーという考え方がなされておる。もっとも簡単な例で言えば、プログラマは単に、std::vectorだとか行列クラスを使った際に、そのパフォーマンスが向上していることに気付くだろう。もっと詳しく見ていけば、move constructorがパフォーマンス向上の理由だということに気がつくだろう。コンテナをmoveすることは、copyよりも安くつく。さらに次のレベルでは、プログラマはmove constructorと、perfect forwardingな関数を書く。これは単に、std::move()とstd::forward()を使えばいいだけだ。ここで、rvalue referenceに対して、何か特別に理解しておかねばならんことなどあるのかね。大方のプログラマは、そこまで深くは首を突っ込まないだろう。思うに、ほとんどのプログラマは、「A::f()&&;とは一体どういう意味なのだ」とか、「std::move()は一体どうやって実装されているのだ」などという疑問が沸くことなどないだろう。そういう疑問は、理解しなければならないという理由があってこそ理解しようと思うのだ。ほとんどのプログラマは、もっと意味のある高級なコードを書くことに時間を費やすのだ。欲しいのは良いプログラマであって、言語ヲタクではない。

君がconceptとrvalue referenceに似たような感じを受けるのは、あながち間違いでもない。両方とも、型システムの問題なのだ。ゆえに、根本的に膨大で複雑なのだ。両方とも、「ユーザーインターフェース」を持っていて、細部が気になるプログラマを遠ざけてくれる。しかし、「concept」は膨大という以上のもので、長期的には、rvalue referenceより重要だ。

Page 3: Do We Still Need Concepts?

Danny Kalev

僕はあまりConceptのファンじゃないのですが、それでも、conceptとrequires キーワードの必要性は分かります。でも何で、concept mapとかaxiomなんかが必要なんですか。僕的には、言語を無意味に複雑化させる、机上の空論で非現実的なオモチャに思えるんです。まあ、役立つ利用方法もあるんでしょうが、最初は基本的なところから始めて、後から、注意深く拡張していった方がいいんじゃないでしょうか。

Bjarne stroustrup

やれやれ。キーワード数の削減が目的ではないのだ。ジェネリックプログラミングをサポートするために必要なのだ。conceptを便利にするには、現行の成功例を明確化してサポートしなければならん。conceptが受け入れられるためには、現行の成功例を完全にサポートしなければならん。さもなければ、constrainedとunconstrainedなtemplateがごっちゃになるだけだ。conceptが受け入れられるためには、「旧態依然のunconstrained」なtemplateから、「concept baseのconstrained」なtemplateへの、簡単な切り替え方法を、提供しなければならん。話はそれるが、ワシのフランクフルト前のコンセプト規格の設計に対するユーザビリティへの懸念は、このことも関係しておる。

concept mapについて考えてみることにしよう。conceptに任意の型を当てはめる、何らかの機構が必要だ。この機能がなければ、そのconceptを念頭に設計された型しか使えん。君が幸運の星の元に生まれたならば、たまたまシグネチャが一致しているかもしれんがね。そもそも、まったくの新機能なのだから、ユーザー同士が協調しあえば、conceptに一致するような型を強制できるだろうと思うかもしれん。ところが、C++というのは、1972年からの歴史ある、広く使われている言語なのだ。すでに多くの、想像もできないような型があるし、毎日毎日、新しい型やconceptが生み出されているのだ。

ちょっとここで言っておくが、ここ十年かそこらで、開発者というのはすでに、conceptを発明しているし、テンプレートをそのconceptに沿って書いているのだ。そういう「concept」は、単に明示化されていないだけに過ぎん。大抵は必要事項などドキュメントも不完全だし、勝手な仮定も含まれているのだがな。

そういうわけで、既存のconcept、例えば、random access iteratorと、既存の型、例えばint *を例に挙げよう。ここで、型を、conceptを要求するテンプレート引数で使いたいとする。一体どうすればいいのだ。言語にconcept mapがなければ、でっち上げるしかない。例えば、RandomAccessIteratorは、メンバー型(訳注:規格的に言えば、クラスにネストされた型)としてvalue_typeがなければならん。RandomAceessIteratorを使うテンプレートコードは、その名前を、引数の型のメンバー名として使うのだ。ところが、int *にはvalue_typeなどというメンバーはない。そもそも、int *にはメンバーなどというものがない。というのは、Dennis Ritcheが、ポインタにメンバーなど必要ないと、1972年に取り決めたからだ。タイムマシンでもない限り、当時int *にメンバーを付け加えるなんてことはできんし、そのためだけに、C with value_type(訳注:C with classesのもじり)などという言語を、1994年の時点で発明することもできなかったわけだ。

言語にconcept mapがなければ、自前のmapを作るしかない。この「map」は、int *のラッパークラスで、value_typeをメンバーに持っていて、そのほかのrandom access 操作もできる。幸運と優れたコンパイラに恵まれれば、int *とまったく変わらないパフォーマンスが得られるだろう。あとは、あらゆるint *を自前クラスのRandomAccessIteratorでラップするだけだ。ジェネリックプログラミングの経験があれば、この手のラッパークラスや、似たようなアイディアである、traitsは、よく見かけたことだろう。このようなworkaroundコードは一般的で、ジェネリックコードではお手軽だ。ただし書くのは面倒だし、大抵は汎用的ではなく、ごく限られたものにしか動かん。エキスパートでなければ理解しづらい。だから問題の元なのだ。

concept_mapは、このような現実的で根本的な問題への、直接的な解決方法なのだ。例えば、

template<class T> concept_map RandomAccessIterator<T*> {
typedef T value_type;
};

これは、T*をRandomAccessIteratorとして使えば、そのvalue_typeはTであるという意味だ。常の如く、どんな文法が一番いいのかということに関しては議論があるが、このようなことを表現する必要がある。「このようなことを表現」するのは、実際に必要なのだ。完璧で汎用的なworkaroundなど存在せん。だから言語機能として絶対に必要なのだ。concept mapなしで、conceptを使ったジェネリックコードなど、書きたいとは思わんよ。

simplifying the use of conceptsでの議論を、また持ち上げるようなことなどしないが、ワシの言いたかったことは、concept mapは無駄に使われすぎで、複雑で使いにくいという事だ。ワシはその問題を解決するための提案をした。何にせよ、存在しない操作を付け加えたり、名前を変えたりして、型をconceptに一致させるための仕組みとしての、concept mapは経験上必要で、議論の余地もないことだ。

すでに説明したように、conceptは指定された文法に対して動く。ある型が、具体的な名前による操作や型を持っていなければならないということを明示するのだ。例えば、random access iteratorとして使うためには、その型は*, ++, [], value_typeなどを提供していなければならんなどだ。常識的に考えて、これらの操作は「正しいことをする」と見なされていて、その「正しいこと」は、どこか別のドキュメントに書かれておる。この場合、C++標準規格だ。これはconceptが言語側でサポートされているかどうかに関わらず、一緒だ。テンプレートに対するドキュメントというのは、意味的な必要事項を書き上げたものなのだ。良いドキュメントほど、その必要事項も詳しく書かれることになる。ここでの問題とは、一体どこまで、意味的な必要事項を、言語側でサポートすればいいのか、またどうやってサポートするのか、ということだ。

axiomでは、コード中に、詳しい意味上の規約を、簡単な文法で記述できる。例えば、

concept ForwardIterator<typename X> : InputIterator<X>, Regular<X> {
  requires Convertible<postincrement_result, const X&>;
  axiom MultiPass(X a, X b) {
   if (a == b) *a <=> *b;
   if (a == b) ++a <=> ++b;
  }
}

この記述は、ForwardIteratorとは、InputIteratorであり、==などを提供するのRegularであり、シークエンスに対して、multiple passという公理が成り立つ。<=>とは同等演算子である。つまり、ForwardIteratorのaとbに対して、aとbが等しいならば、*aに対する操作は、*bに対する操作と等しい。高校の代数学が苦手な奴らは、はだしで逃げ出すことだろう。しかし、記述された論理というのは古典的で、汎用的で、意味を記述するのに効果的だ。

axiomの規格の設計には、二つの目的がある。

  • より詳しいドキュメントを記述させること。
  • プログラマは、コード上で直接、開発環境に情報を伝えられること。

多くの連中は、axiomの目的を、以下のように、間違って考えている。

  • axiomはコンパイラにより良いコードを生成させるためのものである云々。実際、axiomは、特にそういう用途に優れてはおらん。というのも、ワシらが使う型というのは、数学的な法則に従うものではないからだ。doubleのような浮動小数点数は、実数の法則には従わん。実数にはNaNなどないからな。intの類は、整数の法則には従わん。数学的な整数は、オーバーフローなどせん。もし、axiomを最適化の為に設計しておったら、今頃、大失敗しておったことだろうよ。
  • axiomは型を証明するためのものである云々。まあ、数学を勉強する際には、公理というのは、証明をするために使うものだ。さすがにワシも、C++コンパイラに定理の証明機能などというものを提案する気はない。
  • 提案されている述語理論(訳注:predicate logic)の文法で、十分に意味の記述が出来るのか、もっと拡張すべきではないのか云々。述語理論はとても長く、且つ、輝かしい歴史を誇っており、表現力がある。ワシは冒険的に拡張したりなどはせん。

というわけで、axiomの使用目的と、よくある誤解を説明してきたわけだ。では残った利用方法は役に立つのかというと、役に立つと言えるね。conceptのドキュメントには、多くの公理を用いているという現実がある。しかし、利点というのは実際に示すのが難しい。

ここでaxiomの具体的な使い方を説明したりはしないが(詳しくはN2887参照のこと)、conceptを使う上での難しい問題に、文法上はほとんど同じだが、意味が違う、複数のconceptというのがある。たとえば、foward iteratorとinput iteratorだ。ワシは、次のconceptの設計では、もっとaxiomを全面的に押し出して、意味上の違いを、扱いやすくするつもりだ。

Danny Kalev

最近のDDJの記事で、「ConceptはC++0xの根幹をなす新機能だ」って書いてますよね。ある人は、C++0xの「主要機能」だって言い切ってましたよね(N2893)。でも、Conceptが入らないって決まったとたん、なんかやたらとそっけないんですよね。例えばですね。Herb Sutterがブログに書いてますけど、「concept延期に関するWeb上のコメントを読んでると、技術的な不正確さもさることながら、C++0xにConceptが入らないというのは、何かとても重大なことのように考えているらしい。不思議なことだ」とか。結局Conceptなんてどうでもいいことだったんですかね。なんでこんな機能に、アホみたいに時間と人手をかけてたんですかね。

Bjarne stroustrup

ワシが書いていた事を全部引用すると、

「Conceptは、templateの使用をより論理的にしたり、標準ライブラリの規格を書き直したりすることにおいて、根幹をなす新機能であり、また、ジェネリックプログラミングを一般にも使えるようにするための主要な機能だ。」

これは、単に、

「ConceptはC++0xの根幹をなす新機能だ」

というのとは、だいぶ印象が違ってくるな。

それから、ワシとしては、concept自体を「主要機能」だと言ったりはしないな。というのも、あるひとつの機能だけをもって、その言語の「主要」であると言うなどという事は、意味が分からない。ほとんどの人にとって、C++0xの最も重要な新機能とは、並列処理(concurrency)の標準サポートだと思うな。conceptと同じで、taskやatomic typeなどを直接使うのは、ごく一部のプログラマだけだが、恩恵を受ける人は多い。C++0xの改良は、よりよりプログラムを書いたり、開発環境をつくるのに、役立つツールに重点を置いているのだ。単にその機能自体だけですばらしいというわけではない。

ワシが思うに、conceptとは、重要だったし、また将来も重要になるだろう。テンプレートのユーザビリティを向上させ、ジェネリックプログラミングの範囲を広げ、高品質なライブラリのサポートの為の、重要な機能だ。何十年もの長期的にみて重要なのだ。だが、普通のプログラマにおける、この先数年の重要度というのは、それほどでもないだろう。手遅れにならないうちに、conceptがどうしようもなく必要な時代に達しないうちに、改良したconceptを入れることも出来るだろう。

長期的な物の見方が重要だ。そもそも、ConceptはたくさんあるC++0xの改良点のうちのひとつにしか過ぎんのだ。たとえConceptなしでも、C++0xはC++98よりはるかに優れたツールとなるだろう。

Page 4: Commitees and Standardization

Danny Kalev

あなたは何度も、100人の委員会というのは、「革新的な設計に最適な場ではない」と言ってますよね。ぶっちゃけ思うんですけど、ISO標準化委員会ってもうオワッテルんじゃないですかね。RubyやPhtyonは「善き独裁制」(訳注:実装がひとつしかなく、基本的にただ一人の独断で開発されていて、その実装がそのまま仕様になるような言語のことを言うのか)モデルを使って、標準化をしてますよね。Linuxはコミュニティプロセスですよね。もっと何か、今の時代に合った、別のプロセスで標準化することを考えてみてはどうですかね。今みたいなやたら几帳面で面倒臭くてトロいようなのじゃなくて、もっとみんなが直接参加できるような感じの。

Bjarne stroustrup

ワシのペーパーを読んで見給え。標準化と進化のモデルに対する懸念が書かれておる。(HOPL3 paperHOPL2 paper)。それはともかく、ワシは「もっとみんなが直接参加できる」ことが、向上に繋がるとは思えん。言語の議論における、普通の「声」は、大抵、短期的な物の見方をしており、流行に振り回されていて、しかもただひとつの機能しか考えておらん。多くが「簡単で完璧な解決方法」とやらを複雑な問題に適用できると信じており、既存の何十億行ものコード、何百万人ものプログラマへの教育といった事への配慮が足りん。何年も辛抱強く、設計、標準化の為の、多大で面倒な、細部を明確にする仕事をしたいと思う者は、ほとんどおらん。ワシが見たところでは、「コミュニティプロジェクト」とやらは、すでによく知られた概念が存在する物にしか有効ではない。たとえば、LinuxにおけるUnixなどだ。根本的に異なる設計思想から選り分けるのは、難しいことだ。それと、C++の標準化は、完全なボランティア作業であることを知っておいてもらいたい。もしC++が、わずかでも報酬を支払うようになっていれば、また違った物になったかもしれん。

ワシは独裁主義が好きではないが、短期的に見れば、効率的で善政を敷くのかもしれん。だがワシが思うに、ほとんどの「善き独裁者」というのは、大抵その力に飲まれ、一般受けしない個人的な趣味の機能を入れたりするだろう。つまり勝手な独断に走るのだ。

C++では、独裁というのは未だかつて行われておらん。最初期を過ぎれば、ただひとつの実装なんてものは存在しなかった。C++は、そこらのスクリプト言語とは比べものにならぬほど、多くの企業の根幹に組み込まれてきた。さらに、1980年代後半から1990年代には、HP、IBM、Intel、Microsoft、Sunのような企業は、ただ一人の独断に左右されるような言語を、まともなビジネスで使いたいとは思わなかった。今だにそうだと思う。C++標準化の仕事は、主要なユーザーによってなされているのだ。

Danny Kalev

「委員会による設計」というアプローチって、失敗の元ですよね。conceptが批判されてるのもそのためですし。例えばexportですよ。(後で知ったが、John SpicerもBerlin会議で似たようなことを言っている。議事録はここ)。何で委員会が標準化するのを、既存の、有効性が保証されているような物に限定しないんですかね。実際、そうやってSTLが出来たわけですしね。そうすれば、C++0xも、もう少し早く規格制定出来たんじゃないですかね。

Bjarne stroustrup

1996年の時点で、STLを「既存の、有効性が保証されている」ものだと考える人は、それほど多くいたとは思えんな。さらに、ライブラリの提案と、言語機能の提案は、だいぶ違うものだ。ライブラリの提案を予測するのは、比較的簡単だ。

フランクフルト会議で、ワシが指摘したのは、

  • ある人は、「商用実装がないものを標準化するべからず」と主張している。
  • 主要なベンダーは、標準規格なしで実装をしない(商用的に利点がある独自機能でない限り)

この問題を避ける方法としては、実証のための実験的な実装を提供することだ。ただこれは、議論を「十分に完全な実装とはどの程度か」と、「実験的実装の標準規格における準拠度はどのくらい正確か」といった事に変えるだけだ。

Conceptでは、実験的な実装であるConceptGCCと、実際に動くSTLがあった。ある人にとっては、これでは十分ではなかった。ただワシが思うに、多くの人が、実装の問題に気にしすぎていて、設計の問題には無頓着だった。設計と実装だけでは不十分だ。機能を使ったライブラリも必要だ。そのライブラリを使った、実際のプロジェクトも必要だ。まあ、それなら完璧だろうが、そんなに何年も実験ばかりしている暇はない。誰がそんな実験的な実装の上にプロジェクトや製品を作るというのか。仮に誰かがやったとしても、どうやって、そんな早い段階の実装上の経験を元に、微妙に違う標準規格を制定出来るのというのか。得られるのは、非互換な独自機能が増えていくことぐらいな物だ。C++98とC++0xの間の長い空白期間こそが、AppleやMicrosoftが独自「技術」を打ち出している理由なのだ。ただ、何も彼らに限った話ではない。企業というのは、業界のシェアと利益を追求するものなのだ。言語設計の汎用性の為にあるのではない。

従って、一般に新しいアイディアを評価するのは難しいし、広く使われている言語においてするのは、さらに困難である。不可能であるとは思わないが。ワシは大方は、実験的な実装で十分だと思う。たとえ完全ではなかったとしてもだ。機能を100%実装していないかもしれないし、他の機能との兼ね合いが十分ではないかもしれんし、パフォーマンスが最適化されていないかもしれん、デバッグ機能が欠けていたり、エラーメッセージが完全でないかもしれん等等。現実では、どの程度の実験が十分かどうかを判断しなければならんのだ。しかし、これはある機能に必要な物がすべて受け入れられるというわけではない。それに加えて必要なのは、規格の検証、単純性、完全性、汎用性、拡張性、ユーザビリティ、人に教えることが出来るかどうか。

多くの設計案を検証しなければならん。大抵の場合、ほんのわずかな規格設計の違いが、一般プログラマにとっての使いやすさを、遙かに変えるのだ。実装重視で見逃す事が多いのはここだ。ワシは「典型的な利用例」と、「重要な利用例」を集めて、多くの設計を試すことにしておる。WG21サイトにある、initializer listとinitializationに関する多くのペーパーが、このアプローチを実践しておる。さらに、ワシはチュートリアルを書き、生徒に複数の設計案を試させることにしておる。もちろん、他の言語機能との兼ね合いや、将来の拡張を検証することも必要だ。

もっと素早くて単純な「委員会による設計」以外の方法で、しかも金銭的援助が必要なく、企業の影響も受けないような規格設計の方法があると思うのは間違いだ。WG21では、「委員会による設計」を出来る限りなくそうとしておる。ほとんどの設計は個人個人で行われ、みんなの前にもっていって、改良していくのだ。それでうまくいくこともある。

John Spicerはフランクフルト会議でexportの問題を取り上げたし、ワシだって言った。他の連中も言っていたと思うがね。アレはいい例だ。しかし、アレから何かただひとつの教訓めいたものを引き出すことはできん。ワシがexportに関して言うことは、あまり手を広げすぎて、複数の技術案を取り入れることはよしたほうがいいということだな。むしろもっと明確な設計をするべきだ。

Danny Kalev

興味深いですね。C++98のtemplateのコンパイル方法として、現実的に使われているinclusion モデルとexportとが、Douglas Gregorがテキサスとインディアナと言っているのと同じ、二つの思想を一つの提案にまとめようとした結果なのですか。どうもそんな風に一緒くたにするのは、うまくいかないようですね。

Bjarne stroustrup

そうだ。exportの失敗は、templateのinclusion モデル と separate compilation モデルをひとつにまとめようとした事なのだ。ただし、君やみんなに知っておいていてもらいたい事は、当時、どちらの方法も実際に使われておったのだ。標準の方法がどうしても必要だったし、どちらのモデルも強く支持されておった。そういうわけで、折衷案は作りづらかった。こちらを立てればあちらが立たずとな。

省みるに、両方のモデルを絶対にサポートする必要がなかったために、ベンダーはinclusion モデルばかりを実装したんだろうな。EDGはexportを実装している。だが皆、主要なベンダーが実装し終わるまでは、いかに面倒であろうと、使いたがらないようだな。当時の私は、そういうことは考慮しておらんかったし、委員会が失敗したのも、明確な使い分けを考えてなかったことによるのだろう。ただ、委員会を責める前に、ちょっと考えてはくれんか。「今現在問題がある」のと、「将来問題になるかもしれない」のどちらを選ぶかということは、個人としては非常に難しい事なのだ。頭のいい連中同士で考えても難しい。

concept提案の根本的な違いを見分けるのはとても難しい事だ。この問題に気付いておらん者さえおる。難しいのは、「一体何が根本的な違いなのか」ということだ。当然、議論の多くはconcept mapに終始した。すでに示したように、concept mapはmappingの為だけに必要な機能だ。それ自体がすばらしいと考える者もおった。たとえ空で、何の型や操作も定義しないとしてもだ。フランクフルト以前のconceptの規格設計では、空のconcept mapを要求するようなconceptと(explicit concept)、mappingが必要でない限り、concept mapを必要としないconcept(automatic concept)があった。"simplifying the use of concepts"ペーパーでの論点は、concept mapは、曖昧性の解決に適してはおらんということだ。さらに、君の質問の答えとしてはだ。concept mapの正しい使い方に関する議論の結論は、標準化委員会による言語仕様に反映されるべきであり、conceptと曖昧解決の方法が提供されるべきだということだ。別案は、異なる思想のコードを一緒くたにすることであり、あまり使い勝手のいい物ではない。そこで正しい使い方ということに関して、激しい議論があった。

Danny Kalev

C++ユーザーはいい加減にしてくれよとか思ってるんじゃないですかね。この議論とかいい例ですし。conceptだけじゃないんですよ。ガベコレとかリフレクションとかGUIといった、超欲しい機能が、C++には絶対入りそうにないんですよね。その割には、実験的で難解な機能ばっかり議論されてるし。このコメントにあるように、もうC++ってオワッテルんですかね。言語自体が複雑すぎて、もうまともに改良出来なくなってるんじゃないですかね。

Bjarne stroustrup

その「超欲しい機能」とやらが何なのか、異論の多いことが問題なのだよ。ただ怒鳴るように主張だけして、まともに議論せんのだ。連中は分かってるつもりになっておるんだな。ワシは個人的にGCが好きで、C++0xにはGC ABIも入る。ただ残念ながら、標準GUIなどというものは無理だ。なぜなら、世に独自実装が多すぎるからな。多くの業界が、自前のGUIで囲い込みをしたいと思っておる。「実験的で難解」というのは、大抵、「俺の好きな別言語には無い、俺にとっては理解不可能な機能」を意味するのではないかね。かつては、クラス階層と仮想関数が、そういう機能だったな。

C++が終わってるなんてことはない。C++のマネ事をしている連中が言っているのだろう。

C++の標準化の方法は、確かに遅いし難しい。ただ、すでに言ったように、軽いノリの実験的な方法よりはマシだな。別の方法といえば、商業言語の進化は早い。なぜなら、カネもあるし、多数の環境を考慮しなくてもよいし、C++ほどの下位互換性を気にする必要はない。Visual Basicを覚えているかね。C++はVBほど早く進化しないとは、よく言われていたことだ。だが、C++は既存のコードをなるべく壊さないように進化してきたのだよ。ワシは未だに、崇高なベルラボみたいな研究室が、新しいアイディアを実験するには最適の場だと思っておるよ。

多くの現場は、いまだにC++98にすら追いついておらん。これは、他の言語に言えることだがな。

Danny Kalev

将来についてはどう思いますか。これからどうなるのですか。Technical Reportみたいな感じで、Conceptを後から追加するのでしょうか。あるいは、ド素人や、未だ悟りを得ていない愚痴なコーダ―にも使えるような、全く新しい規格を入れるような日が来るのでしょうか。conceptの廃止は、現行ドラフトにさらに大きな影響を与えるのでしょうか。

Bjarne stroustrup

やれやれ、偏見の多い誘導尋問みたいな質問だな。

まず第一に、C++0xは、すでに合意された改良をすべて取り入れて制定される。たとえば、concurrencyのサポート、標準ライブラリの改良と拡張、統一的な初期化、lambda、move semantics、汎用的な定数表現、等等。ワシの書いたC++0x FAQを読み給え。もはやこの段階では、大規模な変更は入らん。C++0xには重要な改良がたくさんある。いくつかは、すでにGCCやMicrosoftなどが実装しておる。新しい言語のように感じることだろうよ。

第二に、C++はすべての流行を追うような事はせん。実用の為に、ゆっくりと互換性を考えながら進化していくのだ。最先端機能満載の言語とか、極端に小さい言語とか、独自機能満載の言語といった、イカした言語が欲しいならば、他にいくらでもあるだろう。ただし、もしこの先何十年も使うプログラムの為に、十分に柔軟で、他よりパフォーマンスがよく、他に例を見ないほど汎用的で、どんなハードでも動くような言語がほしいなら、C++にし給え。

第三に、多くの人が、何らかの方法で、C++におけるConceptの良さと正しい利用方法を見つけるだろう。「後から追加」するなんてことは、誰も考えておらん。C++の標準化はそのようにはなされん。フランクフルト以前のConcept規格に、"simplifying the use of concepts"提案で改良を加えれば、ワシ的には、満足のいくものが、数ヶ月でできあがるだろう。だが、そんなことはせんよ。conceptを基本から考え直して、これまでに得られた経験は元にするとしても、ほとんどスクラッチから規格を書き直して、実験するだろう。まあ、五年単位の時間がかかるだろうな。だがしかし、やるよ。もっとも、conceptは他のアイディアに打ち勝つほどの利点を示さなければならんがな。もしリフレクションの方が優れているのではれば、代わりにそっちに注力することだろう。

conceptがTR(Technical Report)に向いているとは思えん。型システムに深く関わっているからな。TRというのは、それなしでもやっていけるほど分離できるものに対して行うのだ。型システムは、完全にやり遂げるか、何もしないか、の二者択一だ。

Danny Kalev

じゃ、C++に関しては、楽観的な見方をしてるんですね。

Bjarne stroustrup

ワシは昔も今も、注意深く楽観的に考えておる。ワシは、「ヒャッハーッ! 俺の言語、ツール、設計思想をもってすれば、おめーらのチンケな問題なんぞ全部解決してやれるぜ! 明日にでも世界を征服してやる! フゥハハハーハァー!」などといった楽観的な見方はしておらん。ワシはC++が、「ただ単に」、何百万人ものプログラマをサポートできて、多くの環境にソフトウェアの開発環境を提供し、高度なアプリケーションの開発に使われると言うだけで、十分満足だ。「ソフトウェアの環境」というのは、組み込み系だったり、システム開発だったり、リソースの制約が厳しい環境だったり、C++に最適な分野の事だ。C++0xは、C++98よりも、より良いツールになるだろう。

今としては、委員会はC++0xを小綺麗に包装して出荷せねばならん。xは16進数になるがね。そして実装が出そろう。いくつかの機能やライブラリは、すでに出回っておる。そこで、プログラマは委員会の多大な作業の恩恵を得られるというわけだ。

翻訳終了、疲れた。

まあ、色々と言いたいことはある。

当然、私には、「A::f()&&;とは一体どういう意味なのだ」とか、「std::move()は一体どうやって実装されているのだ」などといった疑問がわく。STLがどうやって実装されているのか知らずに使うなんて事はできない。実装というのは、アルゴリズム的ということもあるが、むしろ、もっと根本的な言語としてだ。だからこそ今の境遇があるわけだから、仕方がないことなのだが。

axiomについては、やはりあまり理解できない。なるほど、確かに公理を前提としたコードを書くし、仕様書にも書く。ただし、それをコンパイラがパース可能にしたところで、特に利点はない。公理の妥当性を検証する開発ツールなど、まず出ないだろう。とすれば、もっと人間の読みやすい形で書いた方がいい。それには、わざわざ新機能を付け加えるには及ばない。コメントという優れた文法が、すでに存在する。例えば、

auto concept LessThanComparable<typename T> : HasLess<T, T> {
        bool operator>(const T& a, const T& b) { return b < a; }
        bool operator<=(const T& a, const T& b) { return !(b < a); }
        bool operator>=(const T& a, const T& b) { return !(a < b); }
        axiom Consistency(T a, T b) {
                (a > b) == (b < a);
                (a <= b) == !(b < a);
                (a >= b) == !(a < b);
        }
        axiom Irreflexivity(T a) { (a < a) == false; }
        axiom Antisymmetry(T a, T b) {
        if (a < b)
                (b < a) == false;
        }
        axiom Transitivity(T a, T b, T c) {
                if (a < b && b < c)
                (a < c) == true;
        }
        axiom TransitivityOfEquivalence(T a, T b, T c) {
                if (!(a < b) && !(b < a) && !(b < c) && !(c < b))
                (!(a < c) && !(c < a)) == true;
        }
}

などと書いてあっても、少なくとも私には、どういう意味なのか、コードを一見しただけでは分からない。おそらく現実に使う際には、これを詳細に説明するコメントが、いちいち必要になるだろう。この文法自体が、コンパイラには特に使われる事もなく無視されるのであれば、そのコメントだけで十分ではないのか。

追記:Bjarne Stroustrupにジジイ口調は合わないという声を受信したので、Stroustrupの動画をいくつか見てみたが、確かに合わないと思った。

4 comments:

melpon said...

翻訳お疲れ様です。英語がまともに読めない人なので助かりました。

江添亮 said...

いやー、たとえ英語がまともに読めなくても、原文を読んだ方がはるかにマシだと思いますよ。
この程度の英文を訳すのですら、「ああ、原文の微妙なニュアンスが全然伝えきれない」と感じるのですから、今まで読んだすばらしい翻訳というのが数えるほどしかないのも納得できます。

原文を読むのは一時間もかからなかったのに、訳すのは一週間かかりましたからね。
しかも、途中でいやになって、二日ほど放置してました。

Anonymous said...

翻訳お疲れさまでした。
Danny Kalevはイヤな奴ですね(笑)

コンセプトの改良によって、sort関数が当時目指していたものくらい
簡潔に制約できるようになるといいですね。

# find関数のコード、テンプレートパラメータの閉じ括弧が>ではなく)になってますよ。

江添亮 said...

>find関数のコード
どうやら原文からして間違えているようです。