プログラミング言語のパラダイムの変遷

アセンブリ言語から手続き型言語へ

計算機(コンピュータ)は ビット列 機械語 として読 みだし実行する、ということを実直に行うわけです。そして、そのビットイメー ジをいちいち人間が打ち込むのはあまりに非効率なので、命令コードをその意 味を(なんとなく?)表す文字列(ニーモニック) に対応させ、加え てアドレスや定数なども10進数とか ラベル をふったりして簡単に プログラムが書けるようにしたものがアセンブリ言語 でした。

しかし、これにしても、正直素人にはかなり難しいわけで、すこし考えても以 下のような問題があります。

というわけで、もうちょっとプログラムを抽象化した形で書きたいというのは 自然の流れです。そこで、プログラミング言語(その当時はアセンブリ言語と 対比して「高級」言語 と呼ばれてました)がいろいろ考案されました。 代表的なものが FORTRAN や COBOL です。つまり、これらは、プログラムを 「変数操作 演算 などの一連の処理=手続き 」と とらえ、それを(人間にとって)書きやすいように設計した言語です。

当初はプログラミング言語はほとんどコンパイラ言語 =コンパイラ によって 事前に 実行形式に変換され、その実行形式を必要な時に実 行する、という方式でした。インタープリタ(逐次実行)方式は、当時の計算 機能力からすると処理速度の点 で不利だったからと思われます。
たとえば FORTRAN と は FORmula TRANslation か ら来たもので、数値計算式を簡単にコンピュータで計算できるように設計され ました。四則演算(加減乗除)の数式 はほぼそのまま書き表せますし、 その他についてもべき乗などいろいろな関数がライブラリの形で用意されてお り、簡単に利用できます。

どういうことかというと、 TD4 で足し算をする場合、オペランドに足したい数を指定した上で

     MOV A 2
     MOV B A
     ADD B 3
のような手順を考えないといけないわけですが、高級言語だと
print var1+var2
のような形式で変数 var1,var2 和の出力を得られる、ということです。

変数と型についての考察

アセンブリ言語では、計算の途中経過をデータメモリに一旦おいておいて、等 でデータ処理をしていましたが、一般的なプログラミング言語では、これを 「変数」という概念で再構成しました。よくある例えとしてはデータ=中 身 をいれておける「箱」 のようなもの、ということなのですが、 機械語を知っている場合は、処理をするデータをおいておくメモリの領域 (に名前をつけたもの) と捉えたほうがより分かりやすいと思います。

さて、変数に付随する概念として、多くのプログラミング言語では「型」 というものが登場します。データを変数が受け入れる際、データの型とあっ た変数でなければなりません。型が異なる場合は原則として入力しようとして も代入しようとしても、変数が受け取ることはできなくなっています。プログ ラミングにとって型という概念を知ることはとても重要です。

初期の BASIC を含め、インタープリタで動く言語(主にスクリプト系)は、デー タ型が無いものもあります。この場合、動的に=動作しているまさにそのとき にデータの型が決定して、さらに必要に応じて適宜変換されます。これは計算 機にとっては余分な処理を強いられるわけです(後述)
型というのは、データの性質 と考えてください。そのデータはどう いう特徴をしたデータなのか、それを説明するものです。型が違うと、データ の 計算機内部での表現形式 が異なります。重要なことは、自分が 使いたいデータは何型で、それを格納するための変数を、その型にしなければ ならない、ということです。

しかし、素朴な疑問としてなぜ面倒くさいデータ型というものが存在するか、 ということがあるかもしれません。ざっくり説明すると

各プログラミング言語には、その特徴・使い道に沿った型が用意されています。 FORTRANは科学計算を対象に開発された言語なので、必然的に数に関する型が豊富 にそろっています。

コンパイラって、具体的にどんなことしてるの?

ここで、一般的にコンパイラはどんなことをしているのかを見てみましょう。 基本的に、コンパイラはまずソースプログラムをアセンブリ言語に一旦変換し て、それをアセンブルすることで機械語に落とします。ほとんどの場合、この 時に同時に標準関数のライブラリ(事前に用意された関数の実行モジュール) をリンクすることで、入出力関数などが使えるようになるわけです。以下に例 として FORTRAN プログラムのコンパイル作業の各ステップ毎の結果を示してお きます。
integer i,sum で変数を宣言して、read, write は入出力文で、format で出力 形式を指定して…等ありますが、とりあえず雰囲気だけでも理解してください。
::::::::::::::
fsample.f90
::::::::::::::
program test2
    integer i,cnt,sum
    sum=0
    cnt=1
    write(*,*)'Input integer:'
    read(*,*)i
10  sum=sum+cnt
    cnt=cnt+1
    if (cnt <= i) goto 10
    write(*,20)i,sum
20  format('Sum from 0 to ',i4,' is ',i6)
    stop
end program test2
::::::::::::::
fsample.s
::::::::::::::
	.file	"fsample.f90"
	.section	.rodata
.LC0:
	.string	"fsample.f90"
.LC1:
	.ascii	"Input integer:"
	.align 4
.LC2:
	.ascii	"('Sum from 0 to ',i4,' is ',i6)"
	.text
.globl MAIN__
	.type	MAIN__, @function
MAIN__:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$312, %esp
	movl	$0, 8(%esp)
	movl	$127, 4(%esp)
	movl	$70, (%esp)
	call	_gfortran_set_std
	movl	$0, -12(%ebp)
	movl	$1, -4(%ebp)
	movl	$.LC0, -276(%ebp)
	movl	$5, -272(%ebp)
	movl	$6, -280(%ebp)
	movl	$128, -284(%ebp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_write
	movl	$14, 8(%esp)
	movl	$.LC1, 4(%esp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_transfer_character
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_write_done
	movl	$.LC0, -276(%ebp)
	movl	$6, -272(%ebp)
	movl	$5, -280(%ebp)
	movl	$128, -284(%ebp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_read
	movl	$4, 8(%esp)
	leal	-8(%ebp), %eax
	movl	%eax, 4(%esp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_transfer_integer
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_read_done
.L2:
	movl	-12(%ebp), %eax
	addl	-4(%ebp), %eax
	movl	%eax, -12(%ebp)
	addl	$1, -4(%ebp)
	movl	-8(%ebp), %eax
	cmpl	%eax, -4(%ebp)
	jle	.L2
	movl	$.LC0, -276(%ebp)
	movl	$10, -272(%ebp)
	movl	$6, -280(%ebp)
	movl	$.LC2, -240(%ebp)
	movl	$31, -236(%ebp)
	movl	$4096, -284(%ebp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_write
	movl	$4, 8(%esp)
	leal	-8(%ebp), %eax
	movl	%eax, 4(%esp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_transfer_integer
	movl	$4, 8(%esp)
	leal	-12(%ebp), %eax
	movl	%eax, 4(%esp)
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_transfer_integer
	leal	-284(%ebp), %eax
	movl	%eax, (%esp)
	call	_gfortran_st_write_done
	movl	$-1, (%esp)
	call	_gfortran_stop_numeric
	.size	MAIN__, .-MAIN__
	.ident	"GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-46)"
	.section	.note.GNU-stack,"",@progbits
実行形式である fsampleは長いので実行形式の16 進数表記のリンクをはっておきます。

インタープリタ言語

コンパイラ言語はプログラムを最終的に機械語におとし、それを実行するもの でした。それに対して、インタープリタ言語というものもはプログラムを直接 解釈する翻訳機(これ自身もまたプログラムなのですが)の上で、1ステッ プ(1行)ごと に実行するものです。プログラミングと実行がそのまま の流れで(特に手順を踏むことなく)出来るので、プログラマにとってコンパ イラ言語よりもお手軽ということがあります。マイコンが生まれた当初 BASIC という言語が代表的によく使われていました。
Microsoft の初期の製品が MS BASIC で、その後 MS-DOS, Windows, ... とど んどん製品も会社も発展していった、というのは有名な話です。
その後lisp 等いろいろなものが発明されています。近年ではperl, php, ruby, python のようなスクリプト言語 (あるいは 軽量言語 (lightweight language) ) と呼ばれるものがインタープリタで動いてい ます。Javascript もブラウザ内のエンジン と呼ばれるものが実はイ ンタープリタとして働き、処理しているわけです。

インタプリタ方式はなにがメリットか、ということなのですが;

中間コード:コンパイラ言語とインタープリタ言語との融合

しかし実はコンパイラ言語とインタープリタ言語の間の立場である「中間 コード」 型というものがあります。これは、プログラムをコンパイルをす るのは確かなのですが、その結果は機械語ではなくある程度抽象化した仮 想機械 (VM, virtual machine) で動くコードです。そして、この VM 自 身も勿論プログラムであり、それがインタープリタのような役割をしてプログ ラムが走ることになります。このやりかただと、VM はそれぞれの機械 (CPU/OS) に対して個別につくる必要はありますが、その上で走るプログラムは 機種に依存しなくなるので、(エンド)ユーザーにとっては便利です。

昔は UCSD (University of California at San Diego) pascal での p-code と いうものが有名でした。最近は emacs 上のe-lisp bytecode もありますが、な んといってもJava のバイトコードが今では代表例だとおもわれます。上で述べ た、機種に依存しないという特徴により、Windows でも Mac でも Linux でも 原則として同じ Java のコードが走るわけです。アプレットの場合、VM は (IE や NN などの) ブラウザに組み込まれています。

Java の当初よく言われていたスローガンが "Write once, run anywhere" で、コンパイルは一回だけ でどこでも動くよ、というものでした。最近では、実際には微妙なバージョン の差等で、全くそのままでは動かないことのほうが多いようです。
さらに .NET Framework も (Windows 環境でしか動かないとはいえ) 中間コー ドを採用しています。これは機種間の差異を吸収するというより、言語間 (C++/VB/C#/...) の差異を吸収するためのもののようです。

手続き型言語での構造化プログラミング

さて、(FORTRAN など)高級言語が生まれてから、それらを使っていろいろプ ログラミングされたのですが、人間のすることですからいろいろ間違い(プロ グラミングの誤りを伝統的にバグ といいます)もおこります。そこで、 どうすればバグの無い(少ない)プログラミングができるかがいろいろ考えら れました。時代背景として、このようなプログラミング=プログラム(ソフト ウェア)開発手法への要求が最初に高まったのが、1960年代後半から1970年代 と言われています。計算機が普及し適用範囲が広がり、開発効率 ソフトウェア品質の向上 が求められたわけです。

その中でもよく言われたのが、 「GOTO 文の害」 です。 GOTO 文 とは 機械語レベルの jump (branch という場合もあります) に対 応して、プログラムの流れを制御 するものなのですが、これを不用意 に使うと、手続きの流れがめちゃくちゃになり、全体が見通せなくなってしま います。プログラムの行に名前(あるいは番号)を振るというのも分かりにく い原因かもしれません。例えば以下の FORTRAN の例をみてみてください。 結果がすぐにわかりますか?

ちなみに、このような制御がぐちゃぐちゃに飛びまくって理解しずらいコード のことをスパゲティ・コードと呼んだりします。
program test02
    integer i,cnt,sum
    sum=0
    cnt=1
    write(*,*)'Input integer:'
    read(*,*)i
10  sum=sum+cnt
20  goto 40
30  if (cnt <= i) goto 10
    goto 50
40  cnt=cnt+1
    goto 30
50  write(*,60)i,sum
60  format('Sum from 0 to ',i0,' is ',i0)
    stop
end program test02
そこで、GOTO 排除、言い替えると GOTO を使わなくてもちゃんとプログラムは 書けるための仕掛が考えられました。それがすなわち新しい考え方である 「構造化プログラミング」 で、それにそったプログラミング言語がいろい ろ提唱されました。
そのうちの代表的なものが、構造化プログラミングを提唱したダイクストラ (Dijkstra, Edsger) 等によって設計・開発された ALGOL で、その後Pascal, PL/1 などに繋がっていきます。ちなみに開発された当初の FORTRAN はさてお き、現在の FORTRAN はfor による繰り返しによる構造化は勿論サポートされて います。
構造化プログラミングでは、プログラムの構造、つまり、手続きは小さな手続 きのあつまりで、それらの手続きはさらに小さな操作のあつまりで…というトッ プダウン的にプログラムを書くことを推奨しました。さらに、プログラムの流 れの制御は のみでプログラミングができることが構造化定理(Structure Theorem)として 1960年代後半に数学者ベーム (Corrado Boehm) とヤコビーニ (Giuseppe Jacopini) によって証明されています
時代(前後関係)でいうと、ダイクストラの提唱("Go to statement considered harmful", 1968)よりこちら(構造化定理, 1966)のほうが先です。

ちなみに GOTO は機械語の jump にほぼ対応する ということで、処 理としてはif や for より軽い(というかfor 等も結局機械語レベルではjump で実現されている)ので、処理効率という意味では GOTO のほうがいいのは確 かです。しかし、以下で述べるように、多少スピードは落ちてもバグのはいり にくいプログラミングのほうが重要という考え方が主流になってきて、特に昨 今では計算機能力が非常にあがったので、細かい処理効率の向上は微々たるも の、それよりバグの無いプログラムのほうが大切、という感覚です。それがス クリプト言語の興隆にも繋がっています。

そしてさらに、手続きのまとまりを "procedure" "function"などとモジュー ル化 し、変数の有効範囲 (scope) という考え方もとりいれて、 プログラムに構造をとりいれることに成功しました。
ちなみにプログラミング言語Cも、この構造化プログラミングの流れをうけて、 かつ出来るだけ効率的な実行を可能とし、その上で移植性をできるだけ考慮す るように設計されました。

そもそもC以前はOS自体はアセンブリ言語で書くのがあたりまえ(ハードウェ アに近いところなので他に書きようがない)だったのですが、それではよろし くないということで、AT&Tの Kerninghan と Richie がCを発明し、それ で書いたものが元祖 UNIX (というか、UNIX を書くためにプログラミング言語 Cを作った)で、その流れが現在の Linux 等に受け継がれてきている(現在進 行形)わけです。

もちろん Linux は現在も(最初の boot のところ等の例外を除き)Cで記述さ れています。おそらく Windows もそうだと推察します。Java 等の中間コード 型の言語ではOSは 原理的に書けない です(そのコードが動く VM は いつ誰が用意するの? ということ)。

さらにちなみにCの場合、 ポインタ 構造体 というデー タ型を導入したことがそれまでの言語とは決定的に違うところで、これらによっ てOSの記述が可能になったのですが、それはまた別の話なのでここでは割愛 します。

もちろん Visual Basic も(.NET は後述するオブジェクト指向にかなり寄って いますが)構造化プログラミング言語のひとつといっていいかもしれません。
講義用スタイル
印刷用スタイル