SSブログ

Unix的プログラミングとカーニハン先生(1) [プログラム]

 C言語やUnixを勉強した少し上の世代の人たちに、絶対的な信頼を抱かせる名前と言えば、このカーニハン先生を措いてはいないだろう。彼の書く本は、単に技術の伝達ではなく、当時使われいなかったPDP-11というあり合わせのコンピュータの上でUnixを作っていった人たちが抱いていたUnixの精神を、かんで含めるように丁寧に説明してくれているからだ。

 コンピュータの本で、興奮したり感動したりする、と言ったら、若い人はどう思うだろうか。日本人にはこのような本は決して書けない。いや、アメリカ人だって、ほとんど無理だ。まだ手作りの時代からの創業者が、その会社の草創期の熱気ある時期の精神を伝えようと書いているのだから、そういう幸運な時代はもはや決して来ないのではないかと思う。

 具体的に本を挙げて説明していこう。一番古い『プログラム書法』という本は、まだC言語もできる前のFortranの時代のもので、今でも手に入るものの、さすがに古すぎるので、これは省くとすると、次に来るのは、

1.

『ソフトウェア作法』(カーニハン、プローガー共著、木村泉訳)共立出版社、1981年(原題: Software Tools, 1976.

 この本も、実はプログラミング言語としてFortranを使っている。しかし、既にUnix上でCが動いており、この本の中でFortranをC言語風の書き方でプログラミングできるようにするトランスレータを作り、そのC言語風Fortranを使ってプログラムが書かれているのである。従って、Fortranの知識はなくても、C言語を知っていれば、多少の違和感はあるものの、プログラム自体を理解することは難しくない。

 この本は大部な本で、その中でUnixの基本的なコマンド、特にテキスト処理に関する各種コマンドを、実際にプログラミングしてみせている。それを、出来上がったものを解説するのではなく、非常に簡単なものから順番に、どのようにプログラミングしていったら、Unixで使われているような頑丈なコマンドを作ることができるかを実地に、その作り方を実演してみせているのである。あるいは、それを読者に一緒に作らせている。各節には練習問題があり、発展的な機能拡張の問題が挙げられている。

 入力をそのまま出力するcopyといった単純なコマンドから始まり、C言語風Fortranを純正Fortranに変換するプログラムに至るまで、一つ一つコマンドを作って見せている。しかも、それはプログラミングを解説しているだけではない。

 たとえば、単純に入力された文字をそのまま書き出す、というcopyというコマンドが「なぜ役に立つのか」、という問いを立て、それこそがUnixのプログラミングのスタイルなのだ、ということを1ページほどに渉って説明している。それを読むと、この余りにも単純で、単にオウム返ししているだけのプログラムが、どれほど役に立ち、Unixの基本理念を実現しているか、ということが手に取るように分かるのである。

 このやや古めかしいC言語風Fortranで書かれたプログラム群を、C言語で全部書き直しながら、この本を読むのもおもしろいだろう。実は、同じカーニハンが、C言語の設計者リッチーとともに書いた『プログラミング言語C』の中には、本書でFortranで書かれたプログラムのいくつかがC言語の例として取り上げられている。ただ、『プログラミング言語C』はC言語の教科書なので、必ずしもプログラム例は多くない。やはり、この『ソフトウェア作法』をきちんと読んだ方が、プログラミングの基本を身に付けるにはいいだろう。

(以下、他の本については、また明日続きを書きます。)


プログラムも手書きをするといい [プログラム]

 僕は長いことコンピュータを使ってきたため、ほとんど文字を書くことをしなくなった。そのため、漢字を随分忘れてしまっている。黒板に板書するとき、字を思い出せない可能性が高いので、予めメモの打ち出しを用意しておいて、それを見ながら漢字を書いている始末だ。これは決して誉められたことではない。

 そこで、去年辺りから、できるだけノートや原稿、メモを手書きでノートに書くようにしている。万年筆の世界に半分くらい復帰している。その効用は、しかし、文字を思い出すということではなく、移動が多い中で、どこででもメモがとれ、必要な情報を書き留めておけることにある。ノート一冊を持ち歩けば、授業の構想、論文のアイデア、プログラムの断片、勉強中のプログラミング言語の練習問題など、ちょっとの時間に書き留めておくことができる。会議中などにも、パソコンを持ち込むわけにはいかないので、ノートは重宝する。

 特に、プログラムを作る際に、最初からコンピュータに向かわずに、ノートに構想を書き、またその部分部分を詳細に書いていくようにしているが、コンピュータ上で入力するのと違い、あくまでそれは下書きにすぎない。つまり、どんなに書いても、それが正しいかどうか実行して試してみるわけにはいかない。当然のことだ。

 しかし、この当然のことが、実は逆に大きな意味を持っている。つまり、機械にやらせるわけに行かないので、紙の上でコンピュータ上での実行をシミュレートするようになるのである。変数に値を設定し、繰り返し毎にどのように値が変化していくかを順に書き出し、正しく動くかどうかを検証する。

 自分がコンピュータになったつもりで、自分の手書きのプログラムを実行してみるのである。こうすると、そのプログラムの動きがよくわかるようになる。よくわからないけれども、何となく動いた、などということはあり得ないことになる。

 こうして手を動かして書き、また手を動かして実行しているので、特に新しいプログラミング言語を勉強しているときには、よく覚えることができる。プログラムを覚える、ということの効用については、前に書いたことがある(http://blog.so-net.ne.jp/yfukuda/2005-02-09-1)。プログラムは、口で唱えて覚えるわけにはいかないのだから、やはり書いて覚えるのが一番だ。これをキーボードで入力していたら、たぶん、なかなか覚えることはできないと思う。


リストの何番目かの項目 [プログラム]

 リストを繰り返しに使う、という前回の話は、「繰り返し」というプログラムの重要な構文の説明のためのものだった。

 一方、リストそれ自身については、もっと他の処理がいろいろ可能である。

 まず、リストの中の何番目かの項目を取り出す、というもの。「リストcommand_paramsの3番目の項目」という場合を考えよう。リストの最初の項目は0番目、次は1番目、次は2番目、次が3番目である。たとえば、

リスト・オブジェクト["Tokyo", "Osaka", "Nagoya", "Kyoto", "Sapporo"]にcities_listという名前を付ける。
リストcities_listの3番目の項目を表示する。

リストの一部を部分リストとして別に取り出し、名前を付けることもできる。

リストcities_listの1番目から4番目の項目を取り出して、middle_size_citiesという名前を付ける。
リストmiddle_size_citiesを表示する。

とすると、新たに「"Osaka", "Nagoya", "Kyoto", "Sapporo"」というリストにmiddle_size_citiesという名前を付けることができる。

 新たにリストの一部を変更することもできる。

リストmiddle_size_citiesの2番目の項目を"Kita-Kyushu"にする。

 要するに、リストであっても、単一のデータであっても、書き方が少し違う(ちょっと長い言い回しになる)だけで、新しい名前をつけたり、取り出したり、変更したりすることが可能なのである。


リスト項目の繰り返し処理 [プログラム]

 一群のものをひとまとめにした集合を処理することが、コンピュータはとても得意だ。そのための命令の仕方、つまりプログラムするための言語の構文を説明しよう。

 実は、この繰り返し処理の構文は、様々な言語で構文がかなり違っている。日本語スクリプト言語wythonがベースにしているPythonでは、非常に便利な構文が用いられている。たとえば、

alpha = ["a", "b", "c", "d"]
for x in list:
  print x

これをwythonで書けば、

リスト・オブジェクト[「a」、 「b」、「c」、「d」]にalphaという名前を付ける。
リストオブジェクトalphaから一つずつ項目を取り出し、xと名付け、以下を繰り返す。
  文字列xを表示する。

どうも、慣れてしまえばPythonの方が分かりやすい。

 このプログラムの断片は具体的に、というかバーチャルに考えて、クラスの人を一列に並べ、最初から一人ずつ中に入れて、その人の言葉をプリントする、ということをクラス全員について繰り返す、というような作業を表している。

 リストとその中身の一つ一つの項目という関係を頭に入れておこう。一つ一つの項目は上では順序は特に気にしなかったが、リストとしては、順序がある。それを指定するために、「何番目の項目」というような言い方をする。上と同じことを、「何番目」という言い方で書いてみよう。

リスト・オブジェクト[「a」、 「b」、「c」、「d」]にalphaという名前を付ける。
順番を指定する数値オブジェクトにiという名前を付ける。
iを0にする。
順番iが3になるまで、以下を繰り返す。
  リストalphaのi番目の項目を表示する。
  順番iに1を足す。

「リストオブジェクトの3番目の項目にthirdという名前を付ける。」というように、「何番目の項目」ということで、リストの中の一つの項目を取り出すことができ、それを0から順番に数えていくことで、リストを繰り返し処理できるのである。

 この二つのやり方、すなわち、「リストから一つずつ項目を取り出し、名前を付けて処理する」というやり方と、「0から何番目までiを増やしながら、繰り返し処理する。」というやり方は、リスト全体を処理するためには、さほど違いは見られない。日本語では「一つずつ取り出す」方がやや複雑になるが、考え方ではこの方が素直に繰り返しを表現できる。


プログラミングの問題(条件分け) [プログラム]

<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<!DOCTYPE programming_exercises [
  <!ENTITY HTDP "How to Design Program">
]>




  
    条件判断・場合分け
  

  
 預金額から一年の利息を計算する関数を考えてみよう。
 昔の銀行は、10万円までの預金には一年で4%の利息、50万円までの預金には4.5%の利息、
50万円以上の預金には5%の利息を支払った。
 預金額を与えると、一年で付く利息額を計算して返す関数を書きなさい。
  

  
預金額depositから利息interestを計算する関数calc_interestを定義する。
  もし、預金額depositが10万円より少ないならば、
    deposit * 0.04 を計算して、その結果を返す。
  あるいはもし、預金額depositが50万円より少ないならば、
    deposit * 0.045を計算して、その結果を返す。
  あるいはもし、預金額depositが50万円以上ならば、
    deposit * 0.05を計算して、その結果を返す。
  

  
(define (calc_interest deposit)
  (cond
     ((< deposit 100000) (* deposit 0.04))
     ((< deposit 500000) (* deposit 0.045))
     ((>= deposit 500000) (* deposit 0.05))))

(define (main args)
  (format #t "~a~%"  (calc_interest (string->number (cadr args)))))
  

  
条件によって、場合を分ける。複数の場合に分けには、「もし〜ならば、」から始め、「あるいはもし、〜ならば、」を必要なだけ続ける。
  





もう一つの大事なオブジェクト--物の集まり [プログラム]

 プログラム世界の中にある「物」は、文字列と数値の二つだ、ということで話を進めてきたが、実は、もう一つ(本当は他もあるが)大事なバーチャル・オブジェクトがある。

 「リスト」あるいは「配列」と呼ばれている物だ。これは現実世界で言えば、「集合」とか「グループ」とか「組織」とか「クラス」とか「家族」とか「会社」とか「自民党」とかなどなど。基本になるのは「人」という物だったとしても、現実社会では、何人かをひとまとめにして、いろいろな性格を与え、名前を付けて相互に区別しているだろう。

 プログラムの中のバーチャルな世界も同じだ。同じ種類のものをひとまとめに扱って、それらに名前を付け、その中の一つ一つを背番号化して、番号で処理する。学校のクラスでは、それぞれの人は固有の名前を持っているが、クラスとしては学生番号で一人一人は呼ばれるし、事務処理も名前よりも番号で処理される。それと同様に、数値でも文字列でも、ひとまとめの集合に名前を付け、その中にある一つ一つの物は、番号を付けて区別する。

 こういう番号付きの集合を「リスト」とか、あるいは別の言語では「配列」とか呼んでいる。これもまたプログラム世界の「オブジェクト」の一つである。オブジェクトだから、当然、名前を付ける必要がある。それは文字列や数値と同じだ。たとえば、

文字列のリストにstudentsという名前を付ける。 リストstudentsの1番目の人を取り出し、studentAと名付ける。

というように。

 このような集合をひとまとめに扱えるようになると、プログラムの作業手順の重要な方法である「繰り返し」という処理ができるようになる、ということについては、また後日、説明しよう。


Schemeによるプログラミング入門書『How To Design Programs』 [プログラム]

 昨日の記事「プログラミングの問題集.XML」で<solution>要素に書いたプログラムは、Scheme (スキーム) という言語のプログラムだった。

 というのも、あの問題を取ってきた元本が How To Design Programs : An Introduction to Computing and Programming という本だったのだが、この本はSchemeによるコンピュータ・サイエンスへの入門書だったからである。

 solution要素も、昨日の段階では、ほぼそのまま同書の解答をコピーしたものだった。ただし、pseudo-code要素に書いたwythonによるスクリプトは、若干作り方が異なっていたので、今日はそのwythonスクリプトに会わせた形でSchemeのプログラムを修正した。

 こうやって書いてみると、wythonは実はSchemeに直す場合にも、非常に直しやすかった。

 これは、wythonが素直だということもあるが、問題をできるだけ小さい部品に分解し、それらを組み合わせてメインのプログラムを作る、という手法がうまくいっていることに起因しているようだ。もっとも小さい部品にまで分解されたプログラムは、一文程度の内容になり、そうなると、どのような言語で書いても、ほとんど変わらないことになるようだ。

 もちろん、Schemeのプログラムとして見た場合、もっといい書き方をすべきだろう。いくつかの下請け関数は、メインの関数の中でしか使われないので、補助関数として、メインの中で定義すべきだし、そうした場合、それらに共通のnum-of-visitorsも、letの中のローカル変数のままで、それら補助関数に共有できるようにして、引数として渡さないですむようにできる。

 しかし、wythonはそういう仕様になっていないので、全ての関数を並列に定義し、従って、num-of-visitorsを引数で何度も渡さなければならなくなっている。しかし、wythonでは、効率的でコンパクトな書き方よりも、冗長でも、しっかり「変数」や「代入」、関数呼び出し、引数、戻り値などを意識してプログラムを書くトレーニング用に作っているので、これはこれで仕様ということだ。

 ところで、元本の How To Design Programs は、その頭文字をとって HTDP と略称されるが、MIT (泣く子も黙る、マサチューセッツ工科大学) のコンピュータサイエンスの一番初歩の教科書として有名である。もちろん、本としても売られているが(そして、僕はそれも持ってはいるが)、Web上で全テキストが公開されている。http://www.htdp.org/

 この本で使っているSchemeというLispの方言の一つ(ただし、最も有力な方言の一つ)の処理系も同時に配布されている。DrSchemeという処理系で、PLT Schemeというサイトで公開されている。これは、Windows版、Unix版、MacOSX版が揃っており、さらにそれぞれがエディタまで含んでいるという総合的な処理系(もちろん無料)で、学習用の環境としては非常に整っている。

 今後も、折に触れ、この本から問題をピックアップしていきたいと思う。Schemeもおもしろいし、wythonの勉強にもなるし、プログラム入門用の問題集のデータを集めるたしにもなるし。


プログラミングの問題集.XML [プログラム]

 初歩のプログラミングのための問題を集めてデータベース化することを推奨すると以前に書いたが、テキストで保存しておくとすると、XMLを使うのが便利でいいと思う。

 試しに、次のようなxml文書を作ってみた。

<?xml version="1.0" encoding="Shift_JIS" standalone="yes" ?>
<!DOCTYPE programming_exercises [
  <!ENTITY HTDP "How to Design Program">
]>



  
    問題の分割
  
  
映画館のオーナーになったと考えよう。彼はチケットの料金を自由に設定できる権限を有している。
チケット料金を上げれば、それだけチケットを購入する人は減る。最近の実験で、オーナーはチ
ケットの料金と入場者の数の平均値の間の関係を突き止めることが出来た。チケット1枚の料金を
1000円にすると120人の入場者が見込まれる。チケットの値段を100円下げる毎に、入場者は15
人増加する。しかし、一方では入場者が増えると、映画の放映料も値上がりする。映画一回の放映
で、32万円かかり、それに加えて入場者一人当たり80円の料金を配給会社に支払わなければなら
ない。映画館のオーナーは、利潤とチケットの料金の正確な関係を知り、最も利潤が大きくなるチ
ケット料金を割り出したいと考えている。
  
  
チケット代金額ticket_priceから入場者数を計算する関数how_many_visitorsを定義する。
  ( (1000 - ticket_price) / 100 * 15) + 120 を計算して、その結果を返す。

チケット代金額ticket_priceと入場者数num_of_visitorsから総収入を計算する関数incomeを定義する。
  (ticket_price * num_of_visitors) を計算して、その結果を返す。
 
入場者数num_of_visitorsから支出費用を計算する関数spendingを定義する。
  320000 + num_of_visitors * 80 を計算して、その結果を返す。

チケット代金額ticket_priceから利潤総額を計算する関数total_profitを定義する。
  ticket_priceを関数how_many_visitorsに渡して入場者数を計算し、
      結果をnum_of_visitorsに受け取る。
  ticket_priceとnum_of_visitorsを関数incomeに渡して総収入を計算し、
      結果をtotal_incomeに受け取る。
  num_of_visitorsを関数spendingに渡して支出を計算し、
      結果をtotal_spendingに受け取る。
  総収入total_incomeから総支出total_spendingを引いて、結果を返す。

## メイン ##
チケット代金額ticket_priceを関数total_profitに渡して利潤総額を計算し、結果を表示する。
  
  
(define (how-many-visitors ticket-price)
  (+ 120 (* (/ (- 1000 ticket-price) 100) 15)))

(define (income ticket-price num-of-visitors)
  (* ticket-price num-of-visitors))

(define (spending num-of-visitors)
  (+ 32000 (* num-of-visitors 80)))

(define (total-profit ticket-price)
  (letrec ((num-of-visitors (how-many-visitors ticket-price))
	   (total-income (income ticket-price num-of-visitors))
	   (total-spending (spending num-of-visitors)))
    (- total-income total-spending)))

(define (profit ticket-price)
  (total-profit ticket-price))
  
  
 大きな問題をいくつかの小さい問題に分割し、その一つ一つを解決する関数や式を考え、それらを組み合わせて、最初の大きな問題を解決する。
  



文字列とは [プログラム]

 バーチャル・ロボットを動かすプログラミング言語によって描かれる世界は、文字列と数値というオブジェクトからなっている、ということを前に書いた。

 しかし「文字列」という表現は、プログラムを作る者には、当たり前だが、一般の人には馴染みのない言葉だろう。(それに対して数値の方は、すぐ分かると思う。)そこで、今回は文字列について少し説明しよう。

 ロボットをプログラムするための言語は、基本的には機械に語りかけるための言語である。もちろん、それは我々も理解している必要はあるが、なにより機械が理解できるような単純な文法でなければならない。だからプログラミング言語(プログラムするための言語)は、非常に限られた品詞と限られた構文と、数少ない語彙で成り立っている。

 しかし、そのプログラミング言語で書かれるのは、内容のない、作業手順だけである。内容の方は人間が外から「データ」というロボットにインプットする必要がある。そのデータとは、これはロボットに語りかけるものではなく、人間にとって意味のあるデータである。ロボットに作業をしてもらうのは、人間にとって役に立つ仕事であるから、そのデータは人間をターゲットにしているのでなければならない。

 そこで、プログラミング言語で書かれる手順書の中には、ロボットに命じるための言葉と、人間にとって意味のある言葉が混在することになる。その人間にとって意味のある言葉を、ロボットのための言葉から区別するために、その人間のための言葉を「 」という引用符の中に入れておかなければならない。

 これを文字列と言う。

 これはちょうど、小説における地の文と会話文の違いである。会話文は引用符「」に囲まれている。これは作中の人物同士が会話する言葉である。一方、地の文は、小説の読者に向けて書かれた言葉である。その言葉の向けられる対象が違っている。そのため、それを区別するために会話文は「」に入れるのである。

 プログラムでも、ロボットに何かの処理をさせるための言葉は、そのまま書かれるが、ロボットにとっては意味はなく、人間にとって意味のある言葉は「」に入れて、それをひとまとめにしておく。ロボットは、その内容には一切関知せず、「」があれば、それを文字列という「物」として扱う。

 ロボットにとっては、その内容はどうでもよく、それが文字列という物であることが分かれば、それで作業は続けられる。それを操作するために、文字列に名前を付け、その名前で、内容とは関係なしに文字列をひとまとまりのものとして扱うのである。


関数へのインプットと、関数からのアウトプット [プログラム]

 ある意味では、バーチャル・ロボット全体を一つの関数と捉えることもできる。それが関数である、というのは、その内部がどうなっているかを気にすることかなく、そのロボットに必要なデータをインプットしたら、何らかの処理が行われて、その結果がアウトプットされる、という流れに沿っているからである。

 プログラムは、

 データ → データ → 関数 → データ → データ

という流れで作られているということを説明したが、この関数へのインプットと、関数からのアウトプットとを意識することが、プログラムの理解の大事なポイントである。これは、個々の小さい部品の関数でも、それらを組み合わせた中規模の関数でも、そして、一番大きなプログラム全体を関数と考えたときでも、同様である。

 関数、あるいは下請け作業というのは、「作業」という名前から想像できるように、単に何かの仕事をする機械にすぎない。その仕事の対象になるものは、外からデータとして与えないと、機械は単なる箱でしかない。たとえば、トースターという機械がある。パンを焼く、という仕事を行う。しかし、いくらトースターのスイッチをいれても、パンを入れなければ、ぱんは焼けない。現実世界では当たり前のことである。バーチャルな機械であるプログラム、あるいは関数も同じ事である。作業手順だけは作り込まれているが、データが与えられなければ、何の結果も生み出さない。

 だから、一番下の下請け部品から、機械全体であるプログラム・マシーンに至るまで、何らかのデータのインプットがあり、それが関数の処理を経て、何らかのデータのアウトプットを生み出す、という連鎖がプログラムの流れである。

 関数を使うときの基本的な形は、

a = tashizan(a, b)

という書き方になるが、このとき、a と b が、インプットされるデータであり、tashizanという関数の処理を行った結果がtashizanという関数から戻され、それに新たに a という名前を付けている。これをwythonで書くと、

数値aとbとを、関数tashizanに渡して、その結果を数値aに受け取る。

となる。そこで戻ってきたデータは、再び次の関数のインプットに利用されるのである。


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。