32.10 プログラム実行の処理

ところで,コンピュータはプログラムをどのように動かしているのでしょうか.
OCamlインタープリタに式を入力するとその計算結果しか表示されないので,中でどのような計算をしているのかはブラックボックスのような状態です.しかし,コンピュータの処理は細かく見るとCPUで加減乗除といった演算と条件分岐などの制御を繰り返し行っているだけなので,何項もある数式をいっぺんに処理するわけではありません.ましてやプログラムを書く人が自由に作れる関数などどう扱っているのでしょうか?
そこでここでは,コンピュータがプログラムを処理する手順を模式的に説明します.鍵となるのは計算の実行と関数の呼び出し,式の置き換えです.

プログラムの実行のされ方

例えば,

let square(x) = x *. x
let discriminant(a, b, c) = square(b) -. 4.0 *. a *. c

という関数が定義されていたときに,

discriminant(1.0, -5.0, 6.0)

という式を入力した場合の実行を見てみましょう.

  1. “discriminant(1.0, -5.0, 6.0)”は関数“discriminant”の定義式“square(b) -. 4.0 *. a *. c”の計算になります.
  2. ただし,“a”, “b”, “c”という引数にはそれぞれ“1.0”, “-5.0”, “6.0”という値が渡されて,次の式になります.
  3. “square(-5.0) -. 4.0 *. 1.0 *. 6.0”で,この式は引き算なので,“-.”の左辺と右辺がそれぞれ計算されます.
  4. 左辺の“square(-5.0)”は関数“square”を呼び出して,“-5.0 *. -5.0”になります.
  5. “-5.0 *. -5.0”はかけ算をして,値は“25.0”です.
  6. 一方,引き算の右辺“4.0 *. 1.0 *. 6.0”はかけ算により(正確には“4.0 *. 1.0”をして“4.0”と求めてから“4.0 *. 6.0”を計算することで),“24.0”となります.
  7. よって,“square(-5.0) -. 4.0 *. 1.0 *. 6.0”は“25.0 – 24.0”になり,
  8. “25.0 –. 24.0”は“1.0”なので,
  9. 最初の“discriminant(1.0, -5.0, 6.0)”という入力に対する結果は“1.0”となります.

どうでしょうか?人間が数式を,定義に立ち返りながら,一歩ずつ順番に計算するのと似ていると思います.

計算規則のまとめ

プログラムの実行のされ方を抽象的にまとめると,次のようになります:

  • 整数や小数,真偽値といった最小の要素を値と言います.
  • 変数は値を格納する名前付きの箱のようなものですが,参照されるとその値になるので値と見なします.
  • 値に“+”などの演算子,関数,“if”などを組み合わせたものを式と言います.
  • さらに,式に上のものたちを組み合わせても式と言うので,プログラム全体は式と呼べます.
  • 式は以下の手順を使い簡単にしていくと値になるので,プログラム全体の値が求まり,それを表示します.
  • 式が“式1 演算子 式2”の形になっている時,
    1. “式1’と“式2”を計算して,それぞれの値で置き換えます.
    2. “値1 演算子 値2”の形になったところで,演算子の計算規則から値を求めます.
    3. これが“式1 演算子 式2”の形の式の値です.
  • 式が“関数(式1, 式2, …)”の形になっている時,
    1. “式1”,“式2”,…を計算して,それぞれの値で置き換えます.
    2. “関数(値1, 値2, …)”の形になったところで,関数が呼び出されます.
    3. つまり“let 関数(引数1, 引数2, …) = 定義式”という定義がされていた場合,“定義式”の中に現れる“引数1”,“引数2”,…はそれぞれ“値1”,“値2”,…で置き換えられます.
    4. こうして値の代入が行われた“定義式”の値を計算して,“関数(式1, 式2, …)”の形の式の値になります.
  • 式が“if 式1 then 式2 else 式3”の形になっている時,
    1. “式1”を計算して,値が真偽値であることを確認します(真偽値でなかったらエラーです).
    2. その“式1”の値が,“true”だったら“式2”が,“false”だったら“式3”がそれぞれ計算されます.
    3. そしてその計算結果が,“if 式1 then 式2 else 式3”の形の式の値になります.

練習問題として,“number_of_roots(1.0, 5.0, 6.0)”という式がどのような手順で計算されているのか,紙に書いてみましょう.

関数の呼び出しのトレース

最初にプログラムの実行はブラックボックスのようなものだと説明しましたが,OCamlインタープリタではある程度途中の計算過程を表示してくれる機能があります.さすがに計算手順をすべて書かれても長いだけなので,指定した関数を呼び出す時にその引数の値と関数を計算した結果の値が画面に出ます.
この機能を使うには,

#trace 関数名;;

と入力します.これにより,次のようにすることができます.

#trace descrminant;;return2
discriminant is now traced.
#trace square;;return2
square is now traced.
discriminant(1.0, -5.0, 6.0);;return2
discriminant square square --> 25. discriminant --> 1. - : float = 1.

大雑把ですが,どのような順序でプログラムを実行しているのかがわかると思います.この機能は今後,プログラムが思い通りに動かない状況に遭遇した時に,原因究明の重要な手がかりになります.