この記事は、データ構造 からの続編です。
この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の一記事です。
この記事のポイント
- メモリを仲介して、入出力を連結した複数の処理をまとめて、シンボル化したものを関数と言う。
- 関数を使う流れとしては、以下のようになる。
- 関数の宣言
- 関数の呼び出し
- 関数の処理
- 関数の処理結果の利用
- 関数への入力データを、引数と言う。特に、宣言時の中身が未定の引数を仮引数と言い、呼び出し時の中身が入れられた引数を実引数と言う。
- 引数への中身の渡され方には、コピーされた値が入る値渡しと、値への参照が入る参照渡しがある。
- 関数への参照を引数にとる関数を高階関数と言い、高階関数の引数として参照される方の関数を、コールバック関数と言う。
- 関数名(シンボル)を付けられていない、関数リテラルを無名関数と言う。
関数(function)
そもそもコンピューターは、2進数データを計算回路に入力して、別の2進数データに変換し、出力するものです。
論理回路を組み替えれば、異なる計算回路ができ、組み替え前後で同じ2進数データを入力をしていても、計算結果として異なった2進数データが出力されます。
内部にたくさんの論理回路が配線されたプロセッサへ、オペコードを入力すると、オペコードに従った計算回路となるように、プロセッサ内部の論理回路が組み替えられます。
つまりプロセッサへ、マシンコード化されたプログラムを入力することで、
- プログラムの各行のオペコードに従った計算回路へと組み替える
- 計算回路へ、メモリから2進数データを入力する
- 計算回路から出力された2進数データを、またメモリへ書き込む
を、1→2→3→1→2→3→1→2→3→・・・と繰り返させているのです。
途中段階では都度メモリへ書き込まず、レジスタを利用している場合もありますが、記録場所の違いだけで、流れとしては同じです。
関数は、このようにメモリを仲介して入出力を連結した複数の計算回路を、ひと続きの長い計算回路であるかのように、シンボル化したものになります。(数学の「公式」のようなもの、ととらえても良いと思います。)
関数の処理の流れ
したがって、プログラム中での関数の使い方は、一般に以下のようになります。
- 関数の宣言
- 関数の呼び出し
- 関数の処理
- 関数の処理結果の利用
関数の宣言
ソースコード中で、関数名(シンボル名)に関数リテラルを対応付けます。これを関数の宣言と言います。
データ型の一種として、関数型という考え方もありますが、様々な関数をひとくくりにしても、中身が違い過ぎて、まとめる意味があまりありません。
関数型の宣言では結局、関数リテラルが重要になるので、宣言よりも具体的な中身で関数名を区別
する、という意味で関数の宣言のことを、関数の定義とも言います。
ソースコード中に宣言することで、変数と同じように関数も言語処理系によるデバッグが可能になります。
関数の実行時までには、関数名(シンボル名)に、マシンコード化された関数リテラルの先頭アドレス(仮想アドレス)が対応付けられ、シンボルテーブルに登録されます。
関数の呼び出し(call)
ソースコード中に、関数名(シンボル名)と、その関数への入力データを合わせて書くことで、関数を呼び出します。
高級言語で書かれた関数呼び出し(call)も、言語処理後は、アセンブリ言語のCALL命令と同じリンク動作のマシンコードに変換されます。
また、関数への入力データを、引数と言います。
仮引数
関数リテラル(関数そのもののソースコード)の中に書かれた引数は、変数の形で宣言されていることが多く、宣言だけで中身が未定であることが多いので、特に仮引数と言います。
実引数
仮引数に対して、プログラムの実行時(runtime)に関数へ入力される、中身が実在する引数データは、実引数と言います。関数の呼び出し(call)まで実行(execute)されると、実引数がコールスタックへ積まれます。
関数の処理
プログラムの実行時、関数が呼び出されると、その関数リテラル(マシンコード)の先頭アドレスへプログラムカウンタが入れ替えられ、プロセッサの処理が関数リテラルに移ります。
そして関数の処理結果は、レジスタやコールスタックへ記録されます。
関数の処理が終わると、return(アセンブリ言語のRET命令)によって、プログラムカウンタは、元のプログラムのリターンアドレスへ戻され、プロセッサは関数の次の処理へ進みます。
関数の処理結果(return)の利用
関数の処理が終わり、元のプログラムにプロセッサの処理が戻ると、元のプログラムは、レジスタやコールスタックに記録された関数の処理結果へアクセスできるようになります。こうして元のプログラムは、その関数の処理結果をまた別の処理の対象として使うことができるようになるのです。
実引数の渡され方
元のプログラムから関数への実引数の渡され方には、大きく2つの方式があります。
値渡し
実引数が記録されているメモリ領域から、そのデータ(値)をコールスタック上の関数が使うスタックフレームにコピーして関数処理に使う方式です。
参照渡し
実引数が記録されているメモリ領域への参照データ(仮想メモリアドレス)を、コールスタック上の関数が使うスタックフレームにPUSHして、その参照をたどって関数処理を行う方式です。
●●関数
高階(higher-order)関数
関数を引数にとる関数を高階関数と言います。
「関数を引数にとる」と言っても、関数は、一連の処理が書かれた関数リテラルを、シンボル化しただけのものです。つまりその実体は、「関数リテラルの先頭メモリアドレス(仮想アドレス)を引数にとる」ということになります。
高階関数の引数に、どの関数を指定するかで、当然その結果は変わってきます。
コールバック(callback)関数
高階関数の引数となる方の関数を、コールバック関数と言います。
コールバックとは、直訳するなら「call返し」という意味です。
本体プログラムの処理の中で、高階関数を call する(呼び出す)時、同時にその引数としてコールバック関数を指定します。
その後、call された高階関数にプロセッサの処理が移りますが、高階関数の処理の中で、今度はコールバック関数が call されます。
このコールバック関数は、高階関数の call 元であった本体プログラムで指定していた関数なので、あたかも call されている関数から call した関数へ、 call し返しているような形になります。
このため、高階関数からコールバック関数への call は、コールバック(callback)と言われます。
第一級(first-class)関数
「第一級」という言葉は、first-class(ファーストクラス)の日本語訳です。飛行機のファーストクラスと同様に、個々のプログラミング言語内で最上位レベルであることを意味します。例えば整数型は、様々な計算処理や関数の引数など、最も多くの場面で使えるデータ型であり、「第一級データ型」と言われます。その整数型と同じだけ様々な場面で使える関数は「第一級関数」と言われます。第一級関数は、コールバック関数としても使える必要があります。
無名(anonymous)関数
関数名(シンボル)を付けられていない、関数リテラルを無名関数と言います。
- 関数の戻り値をまたすぐ別の処理に使う
- その関数が使われるのはその処理の一度だけ
- 関数リテラルの内容が単純
このような場合は、関数名を付けて関数を定義(シンボル化)するより、無名関数のまま使った方が、シンボルテーブルの参照を使わないので、より処理効率を上げられます。
この記事のまとめ
メモリを仲介して、入出力を連結した複数の処理をまとめて、シンボル化したものを関数と言います。
関数を使う流れとしては、以下のようになります。
- 関数の宣言
- 関数の呼び出し
- 関数の処理
- 関数の処理結果の利用
関数への入力データを、引数と言います。
特に、宣言時の中身が未定の引数を仮引数と言い、呼び出し時の中身が入れられた引数を実引数と言います。
引数への中身の渡され方には、コピーされた値が入る値渡しと、値への参照が入る参照渡しがあります。
関数への参照を引数にとる関数を高階関数と言います。
高階関数の引数として参照される方の関数を、コールバック関数と言います。
関数名(シンボル)を付けられていない、関数リテラルを無名関数と言います。
次は、オブジェクト(理論編) に進みましょう。
この記事は、絵でわかる プログラムとは何か(6)~シンボル~ の一記事です。
コンピューターのしくみ全体を理解したい場合は、以下の2コースがお勧めです。
日本全国 オンラインレッスン にも対応しています。
知りたいことだけ単発で聞きたい場合は、 オンラインサポート をご利用ください。