第2回 プログラミングについて『プログラミング言語』

『プログラミング言語』
第2回目はプログラミング言語について主にお話をします。
当たり前のことですがプログラムはこの世にコンピュータと名の付くものが出現したときに誕生しました。最初その頃のプログラムとはコンピュータのパネルの端子をつないで行くものでしたので、おおよそプログラミングとはほど遠いものでした。ノイマン博士が提唱したいわゆるノイマン型コンピュータ(プログラムを電気的にコンピュータに内臓し、必要に応じてプログラムを呼び出したり切り替えられるコンピュータ)が出現することで現在の様なプログラムが作られるようになりました。
とは言っても最初から言語などというものがあるはずもなく、1と0を羅列した恐ろしく難解なものでした。それを8進数や16進数で表現したところで事情は同じで、命令を1つ追加する毎にアドレス値を変更しなければならないという面倒なことを行なっていました。
やはり人間は面倒なことは嫌いで、楽をしてなんとかプログラムを作ることができないかということで、アッセンブラ言語が誕生しました。これは1と0の羅列された命令と値を文字に置き換えるもので、基本的には人間が理解し易い文字で表現された命令とコンピュータが理解できる命令(機械語)と1対1に対応した言語です。これをアッセンブラというプログラムで機械語に翻訳するのです。面倒なアドレス計算はこのアッセンブラが行なってくれる様になったのでプログラムの開発効率は向上しました。アッセンブラ言語は現在でもコンパクトで高速なプログラムを作るときなどにはなくてはならないものとなっています。
ところがこの便利な言語には欠点もありました。アッセンブラ言語は対象とするコンピュータのアーキテクチュアに依存しているため移植性がなく、誰でもが目的とするプログラムを簡単に表現することができなかったのです。
例えば次のリストはC言語で書かれた、引数に1を加えてて呼び出し側に返すプログラムをコンパイルしたときに生成されるアッセンブラコードですが、よっぽどの達人でない限り一見して「1を加えて呼び出し側に返しているんだ..」とは分らないでしょう。
_add:
push bp
mov bp,sp
mov ax,OFFSET L00103
call __aNchkstk
push si
push di
mov bx,WORD PTR 4[bp]
add WORD PTR [bx],OFFSET 1
L00102:
pop di
pop si
mov sp,bp
pop bp
ret OFFSET 0
∴このリストはリスティングファイルですので完全なものではありません。
『人間が文章を書くようにプログラムが書けたら..』と、とんでもないことを考えた人がいました。毎日アッセンブラ言語と格闘していたIBMのジョン・バッカス・ナウアーというおじさんです。そのころ誰も考えも信じもしないことを考えてしまったのです。そしてこのおじさんは数年の歳月の後FORTRANというコンパイラを生み出したました。本人は数式翻訳という意味から名付けたFORTRANというネーミングはあまり気に入ってはいなかったということですが、このコンパイラは商用としては初めて作られ成功したものとなりました。
また本人は意図していなかったことですが、この言語はコンピュータのアーキテクチュアには基本的には依存していなかったため、他のコンピュータにもプログラムを移植ができるようになって行きました。
この言語の成功が刺激となって1960年代には多くの言語が誕生しました。事務計算向きなCOBOL、記号処理やAIに使われるALGOL、LISP、汎用なPL/I、PASCAL、C、BASIC、オブジェクト指向のSmall Talk、C++、Adaなど現在でも生き残っているものでも数え切れません。
長い歴史のなかで誕生してた数々の言語には多くの発明がありました。配列を発明したFORTRAN、再帰呼び出しを発明したALGOL、構造化プログラミングのPASCAL、オブジェクト指向言語に大きな影響を与えたSmall Talkなどがその主なものです。
ちょっと話しがそれますが『バグ』という言葉、誰が最初に使ったか知っていますか。 上記のCOBOLを作ったアメリカ海軍の故グレイス・ホッパーばあさんがCOBOLを作る前まだ若かった頃、ハワード・エイケンおじさんが作った機械式計算機のMarkIだったかMarkIIだったかのプログラマーとして働いているとき、この計算機が突然動かなくなってしまうことがあったそうです。原因を調べてみると機械の間に蛾とかゴキブリがはさまっていてこの虫を取り除くこともホッパーばあさんの仕事だったそうです。計算機が動いていないのを見たエイケンおじさんが叱ろうとすると、「虫を取っているところです」と言っていたそうです。これがデバッグの原点だということです。
プログラミング言語は進化してきました。プログラミング言語の揺籃期には『いかに表現するか』に重点が置かれていました、それが一段落すると『いかに開発するか』になり、今では『いかに再利用するか』になっているようです。言語には手続き指向言語とか、オブジェクト指向言語と区別されることがありますが、これもその『いかに』を追求していった結果として進化してきた結果といえます。
手続き指向型の言語にも構造化言語とそうでないものがあります。構造化にはサブルーチンや関数などのサブプログラム単位も大きな要因ではありますが、多分プログラミング言語の命令文のなかで最も忌み嫌われるGOTO文を使わなくてもプログラムが書けるか否かで区別しているような気がします。
例えばC言語は構造化言語と呼ばれることがありますが、表現の仕方ではそうではな
くなってしまいます。次の例を見て下さい。
main()
{
int i;
i=1;
loop:
if(i>10)goto end;
printf("%d\n",i);
i=i+1;
goto loop;
end:;
}
1~10までを表示するプログラムですが、こういう風に表現すれば古風なFORTRANのプログラムと同じで構造化もくそもなくなってしまいます。このやり方である程度の大きさのプログラムを作ると必ずといってもいいほどGOTO文であちこちに飛び回り、作った本人にも理解不能なものとなってしまいます(こういうプログラムをス
パゲッティボウルプログラムと呼ぶのだそうです)。
このような問題を解決したのがいわゆる構造化ということで、
main()
{
int i;
for(i=1;i<=10;i++)printf("%d\n",i);
}
のように、文をみればそこがループになっていて1~10まで表示されるのだとすぐに理解ができ、文の追加や入れ換えがかなり容易に行なえるようになります。
それではオブジェクト指向とは何か? というと、オブジェクトとは何かを考えなければならなくなります。どの言語でも言えることですが基本的にはプログラムには手続き(命令)とデータが混在しています。この2者の関係をどうするかがその分かれ目になります。
例えばオンかオフの値を示す変数をスイッチする場面を考えると、データ(変数)と手続きの関係が疎のプログラムでは(例はC言語で書いています)、
int sw=1;
main()
{
:
:
if(...)sw=1;
:
:
}
sub()
{
:
:
if(...)sw=0;
:
:
}
というのが典型的なもので、どのサブプログラムでも勝手に変数の値を変えています。
こうなると不用意にスイッチしたり、どのサブプログラムがスイッチしているのかが理解するのが大変になってしまいます。
これをデータと手続きを密の関係として表現すると、
int sw=1;
main()
{
:
:
if(...)set_sw(1);
:
:
}
sub()
{
:
:
if(...)set_sw(0);
:
:
}
set_sw(int i)
{
sw=i;
}
と表現すればスイッチを直接行なうは1つのサブプログラムだけになります。がしかし上記の書き方では変数 sw がグローバルですのでサブプログラム set_sw 以外のサブプログラムで sw=1 と書かれると意味がなくなってしまいます。
そこでもう少し工夫して変数 sw と set_sw サブプログラムを別ファイルにすると、
ファイルA:
main()
{
:
:
if(...)set_sw(1);
:
:
}
sub()
{
:
:
if(...)set_sw(0);
:
:
}
ファイルB:
static int sw=1;
set_sw(int i)
{
sw=i;
}
とすると set_sw サブプログラム以外で変数 sw を直接変更することができなくなります。これでもかなりデータと手続きを密にしましたが、 set_sw 以外のサブプログラムが sw の値を直接指定する必要がないので、
ファイルA:
main()
{
:
:
if(...)set_sw();
:
:
}
sub()
{
:
:
if(...)reset_sw();
:
:
}
ファイルB:
static int sw=1;
set_sw()
{
sw=1;
}
reset_sw()
{
sw=0;
}
とすると、より強力に結びつきます。私個人の好みですが上記の set_sw と reset_swの名称は、sw__set と sw__reset として sw というかたまりのなかの set と resetなのだということをサブプログラムの名称でも強調する様にしています。これをそのようにかきかえると、
ファイルA:
main()
{
:
:
if(...)sw__set();
:
:
}
sub()
{
:
:
if(...)sw__reset();
:
:
}
ファイルB:
static int sw=1;
sw__set()
{
sw=1;
}
sw__reset()
{
sw=0;
}
となり理解しやすくなりました。
実はこのファイルBがオブジェクトと呼ばれるもので、1つのかたまりの中にデータと手続きを封じ込めています。
さてこれをオブジェクト指向言語C++で表現すると、
class Sw
{
private:
int sw;
public:
Sw();
void Set();
void Reset();
};
Sw::Sw()
{
sw=1;
}
void Sw::Set()
{
sw=1;
}
void Sw::Reset()
{
sw=0;
}
Sw sw;
main()
{
:
:
if(...)sw.Set();
:
:
}
sub()
{
:
:
if(...)sw.Reset();
:
:
}
と表現できます。この場合ファイルを2つに分けなくても変数 sw (オブジェクト)のデータと手続きはカプセル化されます。
ちょっとした例で示しましたが、オブジェクトとはデータと手続きが1つのかたまりとして扱われ、オブジェクト指向とはそのオブジェクトを中心に扱うことを意味します。またこのオブジェクトを表現するために継承、派生などの様々な機能を持った言語をオブジェクト指向言語と呼ぶ様です。
話しは長くなりましたが、現在私が主に使っている言語はCとC++です。第1回目にもお話しましたが、FORTRANは現在は使ってはいません(パソコン用のコンパイラは持っていますが..)。巷での現在主流のパソコン用の言語にはC++とBasicの様ですが、Pascalを使っている人もいますし、LispやSmall Ta
lkを使っているひともいます。勿論FORTRANやCOBOLだって使われています。
私はどの言語が良いのか悪いのか論ずる気はありません。どの言語を使うかはその人がどの様な環境にいて、何をしなければならないかに左右されます。スーパーコンピュータで科学計算をやっている人は移植性を考えてパソコンでもFORTRANやCを使うだろうし、汎用機で事務ソフトをやっている人はCOBOLを使うと思います。どん
な言語であっても表現できないアルゴリズムはないと言われています。だた問題は表現しなければならない問題にどれだけその言語が合っているのかということと、どれだけその言語に思い入れがあるかということではないでしょうか。
次回からはC言語を主に使いながら、また少し突っ込んだ話しをしたいと考えていますので飽きずにつき合って下さい。