SSブログ

トップダウンとボトムアップ [プログラム]

 プログラム=バーチャル・ロボットを組み立てて行くとき、一つのロボットに割り当てられた作業をいくつかの下請け作業に分解し、さらにその一つ一つの作業をさらに下請けの作業に分解して、一番単純な作業にしてから組み立てていく、という話をした。

 その各段階の個々の下請け作業は、「関数」というプログラミング言語の仕組みで作られている。したがって、プログラム全体は、何段階にも細分化される関数がより集まって一つの仕事を実現するように作られている。

 このように、トップの一つの仕事から徐々に細かい仕事へと分析を進めることを「トップダウン」ということも前に説明した。

 一方、分解された個々の作業を作って、一つ一つの関数を完成させたら、それら組み合わせて、もう一段上の関数を作っていく。それができたら、同じレヴェルで共同し合う他の関数と組み合わせて、もう一段上の関数の仕事を実現する関数を書いていく。このように下の方を完成させて、それを使って、一つ上のレヴェルの作業を処理していく、という考え方を「ボトムアップ」と言う。

 実際にプログラムを書くときには、この、上からと下からという二つの方向の作業は、相互に行きつ戻りつ作っていく必要がある。

 最初は、実現しようとしている機能を分析して、徐々に細かい作業に分けていく。そして、ある程度見通しがついたら、今度は個々の関数を書き、それらを組み合わせて、また関数を書いていく。そのうちに、最初は考えていなかった作業の分割を思いつくこともある。

 この個々の下請け作業をこなす関数は、バーチャル・ロボットの「部品」だと言うことができる。ロボットは大きく三つの部品の組み合わせでできている。その部品はさらにもう少し細かい部品の組み合わせでできている。さらに、その一つ一つの部品も、もっと小さい部品からできている。こうして、一番単純な部品に行き着く。そういう、一つ一つの下請け作業を下請けする部品を作っていくことがプログラムを作ることなのである。


名前とオブジェクト、変数と代入 [プログラム]

 コンピュータ・プログラム世界のバーチャルなオブジェクトを指し示すために「名前」を使うことができる、ということが、プログラミング言語の重要な側面である。我々は、「あるオブジェクトを値とする変数を、それに名前をつけることで識別することができる。」という言い方をする。
 Lispというプログラミング言語の一種であるSchemeでは、defineという語で物に名前をつける。すなわち、 (define size 2) と書くと、Scheme言語の解釈プログラムは、2という値を、sizeという名前に結びつける(→2という値にsizeという名前を付ける。)一度sizeという名前が2という数値に付けられたならば、我々は2という値を「名前を使ってby name」言及することができる。たとえば、 (* 5 size) と入力すると、 10 という答えが返ってくる。

 これは、『コンピュータ・プログラムの構造と解釈(Structure and Interpretation of Computer Program)』の一節である。ただし、超訳。

 コンピュータ・プログラムの世界とそれを表現する言語の関係をどのように捉えるか、なかなか結論を出すことができないでいるが、このプログラミングの名著の中でも、少しぶれがあるようだ。最初は、

  1. 「変数」
  2. その変数を指し示す「名前」
  3. その変数の「値」であるオブジェクト

という三つ組のモデルを示しておきながら、その後の議論では、2という「値」ないしは「数値」に対して、sizeという「名前」を付け、そのsizeという名前によって、2という値ないしは数値を指し示す、という説明になっている。これは「値」あるいはオブジェクトと「名前」という二つのものの間の関係である。

 つまり、値は実際に存在する「物」で(と言っても、もちろんバーチャルな世界でだが)、それを「名前」という観念的なもので表現できるようにしているのである。

 現実の世界の言語も、同様ではないだろうか(ここでは言語論的な視点は考えないことにする)。世界には「物」が存在し、それを言葉で識別して表現するために、バーチャルな「名前」が作られているのである。

 では、
a = 0 a = a + 1

というプロセスはどうなるだろうか。

 オブジェクトと名前という対概念で説明するならば、まず、0という数値オブジェクトに a という名前をつける。

 次に、a という名前で0という数値オブジェクトを表現し、それに1を加え、その結果の数値オブジェクトを指すように a という名前を付け変える。

 このプロセスを変数と名前と値という三つ組の概念で説明すれば、0という値を変数に代入し、その変数にaという名前をつける。

 次に、aという名前のついた変数の中から値を取り出し、それに1を足して、再び、aという名前の変数に入れる。

 こうしてみると、やはり「変数」という概念は、余分な概念であるということが分かる。代入という概念も変数という概念と対になるので、これも余計だいうことになる。

 オブジェクトと名前、名付けと名前の付け替え、こういう概念で一貫してプログラミングを説明できれば、と思う。


プログラムを組み立てる部品としての関数 [プログラム]

 一つの大きな問題を下請けの作業に分解し、それをどんどん奨めていくことで、非常に単純な作業が相互に重なり合ったような構造を作ることができることを、前に説明した。

 やや平板な言い方では、これを「トップダウン」式のプログラミングと言う。

 一方、逆にプログラムの構成要素として、「物」すなわちオブジェクトが一番基本にあることも説明した。そしてプログラムで用いられる「物」は、文字列と数値であることも説明した。これらのオブジェクトの間に、動詞や形容詞で述べられるような様々な関係が結ばれている。

 これらのオブジェクトに作用する動詞や形容詞によって文を作り、それを積み重ねることでもプログラムを作っていける。これを「ボトムアップ」の手法と言う。

 今度は、もう少し視点を変えて、トップダウンとボトムアップのちょうど間くらいのところからプログラムを組み立てることを考えてみよう。プログラムを「関数」の集まりと考える考え方である。

 「関数」というのは、これまで「下請けプログラム」とか言ってきたもののことである。言語によっては「手続き」とか「サブルーチン」とか、「プロシージャー」とか言ったりする。いずれにせよ、ひとまとまりの作業に対して、一つの名前を与え、下請けをさせるものである。

 この「関数」にとって大事なのは、その中身がどのように実行されているかではなく、その作業の結果である。また、オブジェクト・データを中心に作業を進めるとすれば、そのデータを関数に伝え、さらにその結果を関数が得る、というそのやり方も重要である。

 つまり、関数は

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

というように、データの流れの中に入って、データに何らかの処理を施すために使われる。

 y = f(x)

という高校の時に習った関数の表記は、上の流れ図によって書けば

x → f() → y

と書くことが出来る。この場合のf()の中身、その x にどういう処理を施して、結果として y を得るかについては、全くのブラックボックスということができる。

 このような関数には、自分で作ったものと、既に最初からライブラリーに登録されてインストールされているものの二種類がある。特にライブラリーに備え付けの関数は、中身がブラックボックスで、どのように処理されるか考えなくてもよくなっている。要は何をしてくれるかだけを考えればよいし、その使い方も、何らかのデータを渡し、加工されたデータを受け取る、という使い方は、みな共通である。

 使い方に関して言えば、自分で作った関数も全く同じである。ライブラリーに登録されている関数と違うのは、その関数の中身がブラックボックスではなく、自分で処理の仕方をプログラムしないといけない点である。

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

というように使う。この関数tashizanの処理の仕方は

数値xと数値yを受け取って、足し算をする関数tashizanを定義する。
  x + yを計算して、返す。

と定義しておく。また、置換するための関数replaceがライブラリに登録されているとしたら、

文字列strの中で、文字列aを文字列bに置換する関数replaceを呼び出し、その結果を文字列strに受け取る。

というように使う。

 あとは、言葉の語彙を増やしていくのと同様に、ライブラリの関数にどういうものがあるかを調べ、そこにないものは自分で作っていく。またトップダウン方式でプログラムを考えるときには、作業を分割した一つ一つの下請けプログラムを関数という形で定義していくことになる。


スクリプト言語の相互変換 [プログラム]

これは、日本語スクリプト言語を考えている過程で思いついたのだが、Perl、Python、Rubyという三大スクリプト言語のプログラムを相互に書き換えられるツールがあったら便利ではないだろうか。

 wythonは、日本語で書かれたスクリプトをPythonに翻訳するトランスレータであるが、機能は非常に限られ、そもそもライブラリーのようなものも、システム・ライブラリー(本当はPythonではモジュールと言う)や正規表現ライブリーくらいしか使わない。オブジェクト指向もサポートしていない。

 これ以上きちんと作るのが難しいのは、「日本語」スクリプトであることがネックになっているからである。

プログラミング言語をきちんと作ろうとすると、字句解析、構文解析、意味解析を行わなければならない。しかし、さらに難関があって、プログラムをどうやって実行するか、という問題がある。バイナリーにコンパイルして、特定のCPUの特定のOSの機械語にしてしまうというのも一つの手だが、初心者の教育用言語を作るにしてはあまりにも大げさだし、負担も大きい。

 そこで、考えられるのは、バイナリーではなく、別の言語に翻訳することである。これなら、実行コードのことは考えず、別の言語のプログラムを書き出せばよい。

 そういうトランスレータで、おもしろいテーマとして、上に挙げた三つの言語の相互翻訳をできるようにする、というのはおもしろいテーマだ。必ずしも、実用的な効用があるわけではないが。

 実はPerlには、それと似たようなプロジェクトがある。Parrot(パロット)というシステムで、これは、どのようなプログラミング言語を使っても、それを同じバイトコードにコンパイルし、それを同じ一つのソフトで実行させてしまおうという試みである。つまり、Perlのプログラムも、Pythonのプログラムも、Rubyのプログラムも、同じ一つのソフトで動かすということである。(http://www.namikilab.tuat.ac.jp/~sasada/prog/parrot-intro.html 参照。)

 しかし、僕が今言っているのは、実行ではなく、プログラムそのものを別の言語のプログラムに書き換えるツールのことである。

 これは実は思ったよりも大変な作業だと言うこと気付いた。

 まず、それぞれの言語を読み込み、構文解析し、意味解析するまでは、実はそれぞれの言語のコンパイラーのソースコードをそのまま使うことになる。その意味解析の部分で、コンパイラーのアクションとして別の言語のソースコードを書き出せばよい。

 こうすることで、単純なプログラムは変換することができるようになるだろう。しかし、問題はライブラリーだ。

 現在のスクリプト言語は、膨大なライブラリーを最初から用意している。さらにそれぞれのサイトには、ユーザーが作った様々なライブラリーが登録されている。これらを使ったプログラムを変換できないと、やはり実用できないようなものにしかならない。

 もし、そのライブラリーが完全にその言語でのみ書かれていれば、つまりC言語などで書かれていないとすれば、そのライブラリーのプログラム自体も、変換してしまえばいい。そのためには、ライブラリーを探し出す機能を付け加えればよい。あとは、上の構文・意味解析が正しくできていれば、ライブラリーも全部書き直すことができるはずである。

 あとはプログラムの実行は、それぞれ翻訳された言語のプログラムとして処理すればよい。

 こういうことを夢想した。しかし、原理は簡単そうでも、実際に作るとなると非常に大変なことは間違えない。そもそも、意味解析の部分までは、それぞれのプログラミング言語と同じ程度のものなので、実際には相当難しいプログラムになるはず。ということで、このアイデアは、僕は取り組む気にはならない。しかし、誰か、ものすごく勉強したい人がいたら、がんばってみてもいいかもしれない。


これだけ覚えれば [プログラム]

 プログラムを「暗譜」するのが、プログラミングの上達の早道だと言っても、やみくもに覚えればいいわけではないだろう。

 初級において応用範囲の広い、基本的な、ひな形となるようなプログラム、それは単機能の単純なものよりは、もう少し実用的な一定規模の、しかし、よけいな枝葉のないプログラム・ソースコードをいくつか選定する必要がある。たとえば、それは10種類くらいのソースコードがいいだろう。(ただし、10という数に意味があるわけではない。念のため。)

 たとえば、

  1. 算術計算とそれの画面表示。
  2. コマンドライン引数の読み込みと、その内容の表示
  3. コマンドライン引数の値による処理の分岐
  4. キーボードからの入力と、それを使った文字列・数値の処理
  5. ファイルの読み込みとファイルへの書き出し
  6. ファイル名の加工
  7. リストの初期化・リスト処理・リスト全体の処理
  8. 辞書型(ハッシュ型、連想配列)の処理
  9. CGIスクリプト
  10. 可能なら正規表現を使った処理

など。これらについての定型的なコード例を選んで、それを完全に覚えさせ、どれでも、すぐに書けるようになると、きっと初級のプログラム力は十分に身に付いたと言えるだろう。

 もちろん、上に挙げたのは、単なる思い付きにすぎない。きちんと考えるためには、色々な初歩の本に挙げられている例題や練習問題を調査して、そこから望ましい例題はどのようなものかを考える必要がある。


プログラムでも、暗譜がいい [プログラム]

 プログラムを初心者に教えるのにどうしたらいいか、いろいろ考えているが、プログラミング言語も「言語」であるからには、普通の外国語の学習と同様、その文章を暗唱するくらいに何度も読んで、声を出して、独りでに内容がすらすら出てくるようになると、それまでは既にある解答を暗記していただけなのが、突然、自分で自由にプログラムを組めるようになっていることと思う。

 「暗譜」というのは、楽譜を覚えることを意味している。見ながら弾いていたのでは、その曲を自分のものにすることはできないし、そうやって暗譜するところから、その曲を自分のものにしていく過程が始まる。そしてどれだけの曲を暗譜しているかで、その人のレパートリーも決まってくる。

 同様に、キーとなるプログラムの見本をどれだけ覚え、すらすら書けるようになっているかで、その人のプログラムの勉強の進み方も決まってくるのではないか。

 これまで僕は、完全に覚えなくても、前のプリントを見直すという習慣をつければいいようなことを言ってきた。しかし、一々のポイントで常に前のものを見直していたら、結局、自由な発想で問題を解くだけの余裕は生まれない。ある基本的なことをするのに必要なコードの書き方が、自然に頭に浮かんでこないようでは、結局プログラムを書けるようにはならないのである。

 ただし、これは全くの初級の人の話であって、中級や上級の人のプログラムの学び方はまた別のやり方が必要になるだろう。

 


作業を下請けの作業に分割する。 [プログラム]

 さて、一つの仕事をバーチャル・ロボットにさせるためにプログラムを組むとき、その一つの仕事を、より細かい仕事に分割していく、という話をしたが、その一つ一つの分割された仕事ごとに、一つ一つ小プログラムを割り当てていく必要がある。

 下請けの作業を遂行するためのプログラムを組むのだが、そこで組まれたプログラムは、別の業者に発注することになる。もちろん、それはバーチャルな話なので、その「業者」と言っても、それも全体のプログラムを考えている人が自分で何とか処理を考えなければならないのだが。

 その下請けプログラムは、「関数」とか「サブルーチン」とか、「プロシージャー」とか呼ばれる。一般のプログラミング言語の本を読む場合のために、これらの名前は覚えておいた方がいいが、名前はどうであれ、一つの仕事を分割した下請け作業用プログラムであることは変わらない。というその意味は理解しておく必要がある。

 たいていの場合、それら下請けプログラムは、何かを受け取り、そしてその結果を返してくる。たとえば、材料を受け取り、それを加工した、あるいは半加工した結果を送り返してくるようなものだ。それがないと、いろいろな下請けが分担して一つの大きな作業を完成させることはできない。それぞれが勝手なことをしていたら、それらが一つの作業に統合されることにはならないからである。

 今考えているバーチャルなロボットの作業手続きとしては、下請けプログラムが受け取り、もた結果として戻してくるものは、「データ」である。データを渡し、それをもとに何かまとまった作業をしてもらい、結果のデータを返してもらう。そのデータをまた、別の下請けに回すのである。

 回りくどいだろうか。確かに、下請けでやっていることを全部、最初から順にメインの工場でやることはできないわけではない。そうすれば、データを渡したり、返してもらったりするという面倒なロスはなくなる。だが、現実にそんな工場はあるだろうか。あるいはそんな会社はあるだろうか。全部自社製の一貫作業?

 そのように見えても、実はトップのブランドが一つの会社であるだけで、その実、個々の作業は、その関連企業へと分割して発注しているのである。一つの工場が何もかもするのは、非常に効率が悪いし、再利用も他の用途に使うこともできない。その一つの作業に特注の工場を造ってしもうことになる。それは、経営上その会社にとって、命取りになる。

 前にも言ったように、一つの作業をプログラムするとき、ただ闇雲に最初から順番にどうするかを考えるのではなく、大きな仕事を、いくつかの下請け作業に分割し、さらにその一つ一つを下請けに分割する、という企画の立て方が大事なのだ。


問題を分割する。 [プログラム]

 プログラムを作るとき、あるいはバーチャル・ロボットに何らかの仕事をさせるようプログラムするとき、手順を最初から順番に記述していくのは、あまりいい方法とは言えない。

 最初から実行の順番に考えていくのではなく、「問題」という全体をいくつかの小部分に分割し、さらにそれぞれの部分をより小さい部分に分割し、というように大きなところから徐々に詳細な内容へと分割しながらプログラムを作っていくのがいい。

 たとえば、ポーカーゲーム・マシーンを作るとする。

 ポーカーゲームをどのように処理するかを考える。大抵のゲームに共通だが、全体は

1. 準備
2. プレイ
3. 判定

の三つの部分からなっている。このうち、「1. 準備」はさらに、

 1.1 プログラムとしての準備
 1.2 ゲームの準備、つまり、トランプを配る。

に分けられる。また、「2. プレイ」は、実質的には、

 2.1 手持ちの札の交換

であるが、さらにそれは

  2.1.1 手の表示
  2.1.2 何番を交換するかの質問
  2.1.3 札の交換

という三つの作業に分割できる。「3. 判定」も、

 3.1 ユーザーの結果の計算
 3.2 コンピュータの結果の計算
 3.3 結果の比較と判定の表示

に分けられる。そして、それぞれがさらに細かい部分へと分割できる。

 プログラムはこのようにして作っているのである。以上の例は、かなり大きなプログラムの場合であったが、もっと単純なものでも、最初におおよそ、三つぐらいの仕事に分割し、さらにそれを細かくしていく、というように、時間順ではなく、論理的な深浅で積み重ねていく、という考え方を身に付ける必要がある。


プログラミングの例題・練習問題の形式 [プログラム]

 初歩のプログラミングを教えるための例題や練習問題をいろいろ集める場合、それがどのような形式のものかを予め考えておくのだいいだろう。

 これは一種のデータベースだ。データベースであるからには、各データは同じようなフィールドを持っているものとして形式化されていないと、蓄積することができない。

 それでは、例題や練習問題はどのような要素からできているだろうか。実際の本の中では様々な形をとってはいるが、それは次のような要素に分析できるだろう。

  1. 問題の目的、何を説明するための問題・例かということ
  2. 問題そのもの
  3. 解答としてのプログラム・コード
  4. プログラムの解説
  5. 出典

このうち、プログラムの解説は、必ずしもなくても構わない。またプログラム・コードも、練習問題に解答がついていないときには、基本的には欠けていると言っていい。ただし、それを補うために、何とか解答例を考えることはできる。

 問題そのものについては、例題のプログラムの場合には問題がないことが多い。しかし、プログラムの内容が理解できれば、それに基づいて問題を逆に作り出すこともできるし、何とかそうした方がいいであろう。

 目的は、たとえば、「ファイルの入出力の学習のため」とか、「繰り返しの仕方を学ぶため」など。これがいわば分類項目になる。

 ここでプログラム・コードは、特定の言語であることを前提としない。つまり、Cでも、Javaでも、Perlでも、Pythonでも、場合によってはJavascriptで書かれていてもよい。もちろん、初歩の例題・練習問題であるから、短いものが多く、中身も基本的なものとなっていだろう。そのため、それらを別の言語で定義し直すこともできるだろう。必ずしも全部ではなく、汎用の命令構文を全体に適用されずいい。


プログラムの簡単な例題・問題を集める [プログラム]

 初歩のプログラムの教え方を考え、テキストも作ろうとしているが、何が一番難しいのか、あるいは大変なのかを考えてみたら、おもしろく、ためになり、取り組みやすい豊富な例題と練習問題を考えることだということに思い至った。

 考えてみれば、説明というのは、頭の中から文章を引っ張り出し、それを整理し、あるいは推敲することで何とかなるのだが、例題・練習問題を大量に考えるのは、ものすごく大変だ。しかし、それが適切に配置されていると、説明など簡略でも、十分身に付くのではないかと思う。

 たとえば、ピアノのレッスンで、どんなに理論を説明しても、適切な段階を追った練習曲がなかったら、絶対にピアノを弾けるようにはならない。外国語も、どんなに文法を分かりやすく丁寧に説明したとしても、文章を読み、練習問題をやり、会話をしなかったら、絶対に身に付くはずはない。

 プログラムは、それらよりは理論的なものであるとはいえ、これも「言語」の一種であり、その言語を通して世界を整理する見方を身に付けることが、目標である。プログラミング言語理論の研究ではないのだから、やはり練習は必要であり、しかもそれを自分の目や手の代わりに使えるようにするには、訓練が必要なのである。

 それはそうなのだが、それにしても、問題を一人で考えていたら、そんなにバラエティに富んだものを集めることはできない。そこで、そういう初歩の入門用のテキストから、適切な(あねいは適切ではないにしても、とにかくたくさんの)例題・練習問題を集めて、整理し、分類したような資料のストックがあれば、それを参考にして、段階的にうまく問題を積み重ねていけば、その問題だけで、自然に言語が身に付くようなものができるのではないかと思う。

 問題を整理するには、たぶん、問題とその正解をカード化し、それが何の問題なのかを分類していくことから始めるのがいいだろう。


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