2018-06-27

プログラミング入門書の執筆という手探りの活動

ここ最近、まともにブログを書けていない。最新のC++の提案も追えていない。それもこれも、C++によるプログラミングの入門書を書いているためだ。

およそプログラミングが個人でもできるようになって何十年もたとうとしているのだから、いい加減にプログラミングの入門書を書くお作法が成立しても良さそうなものだが、そういった定石は一向に確立されていない。名著と呼ばれる入門書は何冊もあるが、どれもその時代に特化した記述をしていて、その構成を模倣しても現代の入門書としては不適切だ。

結果として、入門書の執筆は自分の感性を信じつつ手探りで書き進めることになる。

よくC++の教育において批判されるのは、ポインターや配列といった低級な要素を最初に教える時代錯誤な点だ。たしかに、現代のC++はポインターや配列を使わなくても書ける。しかし、アドレスやメモリ上の連続したオブジェクトといった概念を理解しないまま優秀なコードが書けるだろうか。ポインターはアドレスを扱うには文法上の罠が多く、配列もメモリ上の連続したオブジェクトを扱うには文法上の罠が多い。

今書いているC++の入門書では、ポインターも配列も教えずに、vectorとイテレーターを教えている。しかし、コンテナーのカテゴリーやイテレーターのカテゴリーについては教えていないので、vectorとランダムアクセスイテレーターを前提にした記述になってしまう。

本当はコンテナーのカテゴリーやイテレーターのカテゴリーについて網羅的に教えたいのだが、そうすると具体的なコードがなく抽象的な分類とサポートされる操作といった文章が延々と続くことになってしまう。入門書では直接的なコードがすぐに書けるほうがよいと判断したのでvectorと実質ランダムアクセスイテレーターだけを教えているが、どうにも釈然としない思いがつのる。

プログラミングの入門書の冒頭では、どのようにしてコードを実行するかについて説明するものだ。既存の毎年改訂版が出るような使い捨ての入門書では、この説明のために、例えばVisual Studioのインストール方法の解説からはじめる。しかもご丁寧にいちいちスクリーンショットを載せ、ここのボタンをクリックしてなどといった本当に何のためにあるのかわからない解説が続く。

私が書いている入門書では、そのようなスクリーンショットは一切載せない方針にした。さらに、今年出たばかりの新しいツールに依存してしまうと、数年後に時代遅れになって使えなくなってしまう可能性が高いので、20年前に持っていっても使えるような安定したツールだけを使うことにした。その結果、使うツールはbashとGCCとGNU Makeになった。20年前から変わらないビルドシステムは、おそらく20年後もこのまま使えるはずだが、印刷される書物としてタブ文字と空白文字を区別するように説明するのはつらい。

今書いている入門書はあらゆることが手探りで進められている。例えば変数の宣言方法は以下のとおりだ。

// 整数
auto x = 0 ;
// 浮動小数点数
auto y = 0.0 ;
// 文字列
auto z = "hello"s ;

変数をいきなりこのような方法で教えるC++の入門書は今まで読んだことがないが、思うに最近の言語は強い静的型付けがないか、強い静的な型推論がある言語ばかりで、このような変数の書き方が自然になってきているので、これでよいだろう。

この調子で、最初に教える関数も以下のようになった。


auto plus = []( auto x, auto y ) { return x + y ; } ;

C++では、厳密にはこれは関数ではなくてラムダ式なのだが、こう書くことによって型を意識しないですむ。

ラムダ式を最初に教えることで、関数を渡す関数も自然に教えられるようになった。


int main()
{
    auto for_each = []( auto first, auto last, auto f )
    {
        for ( auto iter = first ; iter != last ; ++iter )
        {
            f( *iter ) ;
        }
    } ;

    std::vector<int> v = {1,2,3,4,5} ;

    for_each( std::begin(v), std::end(v),
        []( auto value ) { std::cout << value ; } ) ;
}

実際、std::for_eachは概ねこのような実装になっているのだと教えている。もちろん、これは本当のstd::for_eachの実装ではないし関数でもないので、厳密には違うが、初心者ならばこの説明でいいだろうと妥協している。

さらに一歩を踏み込んで、vectorの初期化を以下のようにできるのだが、おそらく混乱するだろうからまだやめている。これはC++17で採用されたdeduction guideによるものだ。


// std::vector<int>と同じ
std::vector v = {1,2,3,4,5} ;

今、ようやくアルゴリズムまで進んだのだが、今までに教えたことしか使っていない結果、関数はラムダ式で、コンテナーはvectorしか教えておらず、イテレーターはランダムアクセスイテレーターで、クラスはまだ教えておらず、テンプレートも当然教えていない。lvalueリファレンスだけは無理やり教えたがCV qualifiersは教えていない。

この状態で一体どこまで教えればいいのだろうか。C++を本格的に使うには、当然既存のC++のコードも読む必要があるだろうから、ポインターや配列を理解しなければならず、クラスやテンプレートも学ぶ必要がある。動的メモリ確保も必要だ。しかしこれらの概念を網羅的に解説するのはあまりにも抽象的すぎて、今想定している入門書では難しい。

今書いている入門書は本当に読者に想定していることが少ないので、C++のビルド方法からはじめ、エラーメッセージの読み方も解説したし、gdbの簡単な使い方も解説したい。

それにしても、今はC++を学ぶ対象読者の想定が難しい。一昔前ならばC言語をすでに学び終えた人間を想定すればよかったのだが、今はJavaScriptとかPythonかRubyぐらいしか触ったことのないプログラマーが増えている。彼らはコンパイルしてリンクして実行という仕組みに触れたことがない。

さらに、一昔前ならば想定することができなかった競技プログラマーという人種がいる。彼らは数学力は凄まじいがプログラミング言語には大して執着がなく、必要な計算を最小限のコードで実現できればそれでいいというとても割り切った使い方をする。プログラミング言語の文法をほとんど理解していないのに問題を解くコードは書けるというよくわからない人種だ。

そして入門書は浅く簡略化した解説を続けながらアルゴリズムまで来た。

この後はgdbの使い方、クラス、テンプレート、ポインター、動的メモリ確保、派生クラスあたりの順番で書こうかと思っている。C++の機能の依存関係の解決が辛い。すでに教えている機能だけを使う縛りを入れているので、早く機能を教えないといつまでも簡略化した説明を続けるハメになるが、いきなりポインターを教えても実感が沸かないだろうから、ポインターは相当後になる。

12 comments:

Anonymous said...

たまに思うのですよね。BASICはなぜプログラマを養成できたか。
それは言語が滅茶苦茶でも触っていて面白いからなのだろうと思います。
N88BASICの時点で割と簡単に絵と音が出せました。つまりゲームが作れました。
コンピュータによってすごいことができる!という興奮。
その興奮をリアライズするのにあれくらいの環境が必要なのです。
BASICから50年やっとC++はBASICの片鱗の絵を手に入れようとしています。
長い凪でした。
もう簡単なゲームを作るのに表面上はOSを叩く必要がなくなるのです。
もう30行以上の無駄な初期化コードを書かなくてよくなるのです。
長い幼児返りでした。
よく考えたらアップルIIはオーパーツだったのですよ。
あれがなかったらあそこまでBASICは高機能にならなかったでしょう。と思っています。

Anonymous said...

前から思っていましたけど、江添氏はauto教の信者ですかね。

autoなんて理解している人が楽するため、記述量を減らすためのモノ。型や宣言式の右辺左辺を理解していない人にイキナリ教えるものではないと思いますよ。何処まで行っても静的型付け言語なのだから、まずは型ですよ。ポインタならともかく。

理解し難いモノと楽するモノは対極ではないですよ。理解し難いモノに対になるのは理解し易いモノであって、autoは理解し易いモノではなく、楽するモノです。単純に言葉の意味で考えてみて下さい。型を知らない人が何が「自動」なのか理解できない上に、自分勝手な解釈を入れてきますよ。

下手なプログラム解説お得意の「おまじない」とでもしときますか?

Anonymous said...

>すでに教えている機能だけを使う縛りを入れているので
文章を読んだ感じ、autoの説明はなされているのだろう

>型を知らない人が何が「自動」なのか理解できない上に、自分勝手な解釈を入れてきますよ。
既に型の説明はなされているのだろうから、まともに読んでいればこの段階で「型を知らない人」にはなりえないのではないか

それとこれは個人的な意見なのですがイテレーターの型が一生懸命かかれているコードよりも
autoと書かれていた方が理解し易く、そして楽です

Anonymous said...

>既に型の説明はなされているのだろうから

>変数をいきなりこのような方法で教えるC++の入門書は今まで読んだことがないが、思うに最近の言語は強い静的型付けがないか、強い静的な型推論がある言語ばかりで、このような変数の書き方が自然になってきているので、これでよいだろう。

と、あるから最初に型の説明がなされているとは思えないのですけど?ポインタや配列の代わりにiteratorやvectorを使うのは良い方法だと思いますけどね。
プログラミング入門書(フローや構造的な解説優先、プログラミングとは何ぞや)ではなく、C++プログラミング入門書(言語仕様も含む)なのだから、型の説明はその後の解説の全てに繋がると思いますよ。それこそ、auto、ポインタ、配列などに。

個人的には、入門書の必要性は余り感じないですけどね。言語のリファレンスと具体的な意味のあるコード例があれば、初心者であっても大体事足りると思います。でも、もうそういう時代ではないのでしょうね。

Anonymous said...

>すでに教えている機能だけを使う縛りを入れている
>変数をいきなりこのような方法で教える
これをどう解釈したら説明がなされていない(教えていない)と読めるのかが疑問でならない・・・

Anonymous said...

Visual Studioのインストール方法は不要だが、
bashとGCCとmakeを使う方法以外にもIDEを使うやり方もあるってのは書いてほしいなあ。
クッソ頭が固い大学の情報演習じゃないのだから、そういうのもあるってのだけは伝えるべきじゃないか?

それはそうと、autoはとても良いものだ。
型が分からんだって?
マウスカーソルを合わせたら型名が表示されるだろ?
IDEを使ってないだって?
プログラミングの初心者がC++を勉強しているのに、どうしてそんな縛りプレイをしているんだ?

Anonymous said...

汎用を目指した結果、誰も読まない本になりそうですね。

Anonymous said...

初心者がautoばっかり使ったらどういうミスするんだろうな。想像もつかない。

Anonymous said...

なんかlispの解説書みたいですね
いっそlispでも最初に教えちゃったら
良いんじゃないですか?捗りますよ。

Anonymous said...

どうなるかはともかく、一度C++の初心者に読んでもらって評価をした方が良いと思いますね。
初心者本のみで本当に学べるのか…
少なくともLinuxの基礎知識が最低ラインになりそうですけど

Fujii said...

auto v = std::vector({1,2,3,4,5});
と書いたほうが一貫性があって、関数型言語とかとも似てますね。

Anonymous said...

> それにしても、今はC++を学ぶ対象読者の想定が難しい。一昔前ならばC言語をすでに学び終えた人間を想定すればよかったのだが、今はJavaScriptとかPythonかRubyぐらいしか触ったことのないプログラマーが増えている。彼らはコンパイルしてリンクして実行という仕組みに触れたことがない。

JavaScriptに限って言えば、トランスパイル(プリプロセス、Cプリプロセッサと違って構文を理解し人間に読みにくいコードを出力することもあるのでもっぱらコンパイルのようなもの)とバンドル(リンクという言葉の再発明)があるので、比較的馴染みがあるように思います。