関数(コンピュータプログラミング)
コンピュータプログラミングにおいて、関数(または手続き、メソッド、サブルーチン、ルーチン、サブプログラム)は、明確に定義されたインターフェースと動作を持ち、複数回呼び出すことができる ソフトウェアロジックの呼び出し可能な単位[1]です。
呼び出し可能なユニットは強力なプログラミングツールを提供します。[2]主な目的は、大規模で複雑な問題を比較的認知負荷の低いチャンクに分解し、チャンクに意味のある名前(匿名でない限り)を割り当てることです。賢明な適用により、ソフトウェアの開発と保守のコストを削減し、品質と信頼性を向上させることができます。[3]
呼び出し可能なユニットは、プログラミング環境の複数の抽象化レベルに存在します。たとえば、プログラマーは、同様のセマンティクスを実装するマシン コードにコンパイルされるソース コードで関数を記述する場合があります。ソース コードには呼び出し可能なユニットがあり、マシン コードには関連する呼び出し可能なユニットがありますが、これらは異なる種類の呼び出し可能なユニットであり、意味と機能が異なります。
用語
呼び出し可能な各用語 (関数、プロシージャ、メソッドなど) の意味は、実際には異なります。これらは同義語ではありません。ただし、それぞれがプログラミングに共通する機能を追加します。
使用される用語は、使用される文脈を反映する傾向があり、通常は使用されている言語に基づきます。例:
- サブプログラム、ルーチン、サブルーチンは以前はよく使われていましたが、現在はあまり一般的ではありません。
- ルーチンとサブルーチンは本質的に同じ意味を持ちますが、サブディレクトリが構造的に親ディレクトリに従属しているのと同じように、階層的な関係を表します。プログラムとサブプログラムも同様に関連しています。
- 関数は副作用のない数学関数を意味すると考える人もいますが、多くの文脈では関数は呼び出し可能な任意の関数を指します。
- Visual BasicとAdaのコンテキストでは、サブルーチンまたはサブプロシージャ
Sub
の略語である は、値を返さない呼び出し可能オブジェクトの名前ですが、 は値を返します。Function
- C#やJavaなどのオブジェクト指向言語では、メソッドという用語をオブジェクトのメンバー関数を指すために使用します。
歴史
呼び出し可能ユニットのアイデアは、ジョン・モークリーとキャスリーン・アントネッリがENIACの作業中に最初に考案し、1947 年 1 月のハーバード大学シンポジウム「 EDVAC型マシンの問題の準備」で記録されました。[4] モーリス・ウィルクス、デビッド・ウィーラー、スタンレー・ギルがこの概念を正式に発明したと一般に考えられており、彼らはこれをクローズド・サブルーチンと呼び、[5] [6]オープン・サブルーチンまたはマクロと対比しました。[7]しかし、アラン・チューリングは1945 年の NPL ACEの設計提案に関する論文でサブルーチンについて論じており、リターン・アドレス・スタックの概念を発明するまでに至っています。[8]
サブルーチンのアイデアは、計算機がしばらく存在してから考え出されました。算術命令と条件付きジャンプ命令は事前に計画されており、比較的ほとんど変更されていませんが、プロシージャ呼び出しに使用される特別な命令は長年にわたって大きく変更されています。Manchester BabyやRCA 1802などの初期のコンピューターとマイクロプロセッサーには、サブルーチン呼び出し命令が 1 つもありませんでした。サブルーチンを実装することはできましたが、プログラマーは各呼び出しサイトで呼び出しシーケンス (一連の命令) を使用する必要がありました。
サブルーチンは 1945 年にKonrad ZuseのZ4に実装されました。
1945年、アラン・M・チューリングはサブルーチンの呼び出しと戻りの手段として「bury」と「unbury」という用語を使用しました。[9] [10]
1947年1月、ジョン・モークリーはハーバード大学とアメリカ海軍兵器局の共催による「大規模デジタル計算機シンポジウム」で概要を発表しました。ここで彼は直列操作と並列操作について論じ、
...マシンの構造は、少しも複雑である必要はありません。この手順に不可欠なすべての論理特性が利用可能であるため、サブルーチンをマシンが認識しているメモリ内の場所に配置し、簡単に呼び出せるようにするためのコーディング命令を開発することが可能です。
言い換えれば、サブルーチン A を除算、サブルーチン B を複素乗算、サブルーチン C を一連の数値の標準誤差の評価として指定し、特定の問題に必要なサブルーチンのリスト全体にわたって指定することができます。... これらのサブルーチンはすべてマシンに保存され、コーディングに示されている番号で簡単に参照するだけで済みます。[4]
ケイ・マクナルティは、ENIACチームでジョン・モークリーと緊密に協力し、第二次世界大戦中にプログラミングしていたENIACコンピュータ用のサブルーチンのアイデアを考案した。 [11]彼女と他のENIACプログラマーは、そのサブルーチンを使ってミサイルの軌道を計算した。[11]
ゴールドスタインとフォン・ノイマンは1948年8月16日にサブルーチンの使用について議論した論文を執筆した。[12]
IBM 1620、Intel 4004およびIntel 8008、PIC マイクロコントローラなどのごく初期のコンピュータやマイクロプロセッサには、戻りアドレスを格納するために専用のハードウェア スタックを使用する単一命令のサブルーチン呼び出しがあります。このようなハードウェアは、数レベルのサブルーチン ネストしかサポートしていませんが、再帰サブルーチンはサポートできます。1960 年代中頃より前のマシン (UNIVAC I、PDP-1、IBM 1130など) では通常、呼び出されたサブルーチンの最初のメモリ位置に命令カウンタを保存する呼び出し規約を使用します。これにより、任意の深いレベルのサブルーチン ネストが可能になりますが、再帰サブルーチンはサポートされません。IBM System/360には、保存された命令カウンタ値を汎用レジスタに配置するサブルーチン呼び出し命令がありました。これは任意の深さのサブルーチンネストと再帰サブルーチンをサポートするために使用できます。Burroughs B5000 [13] (1961) は、サブルーチンの戻りデータをスタックに格納する最初のコンピュータの1つです。
DEC PDP-6 [14] (1964) は、アキュムレータまたはインデックスレジスタによってアドレス指定されたスタックに戻りアドレスを保存するサブルーチン呼び出し命令を備えた最初のアキュムレータベースのマシンの1つです。その後のPDP-10 (1966)、PDP-11 (1970)、VAX-11 (1976) ラインもこれに続きました。この機能は、任意の深さのサブルーチンネストと再帰サブルーチンの両方をサポートしています。[15]
言語サポート
ごく初期のアセンブラでは、サブルーチンのサポートは限られていました。サブルーチンは、互いに、またはメイン プログラムから明示的に分離されておらず、実際、サブルーチンのソース コードは、他のサブプログラムのソース コードと混在する可能性がありました。一部のアセンブラでは、呼び出しシーケンスと戻りシーケンスを生成する定義済みマクロが提供されていました。1960 年代までには、アセンブラは通常、インライン サブルーチンと、リンク可能な個別にアセンブルされたサブルーチンの両方に対して、はるかに洗練されたサポートを備えていました。
ユーザーが作成したサブルーチンと関数をサポートした最初のプログラミング言語の 1 つはFORTRAN IIです。IBM FORTRAN II コンパイラは 1958 年にリリースされました。ALGOL 58やその他の初期のプログラミング言語も手続き型プログラミングをサポートしていました。
図書館
この面倒なアプローチにもかかわらず、サブルーチンは非常に便利であることが証明されました。サブルーチンにより、同じコードをさまざまなプログラムで使用できるようになりました。初期のコンピューターではメモリは非常に不足していたため、サブルーチンによりプログラムのサイズを大幅に節約できました。
初期のコンピュータの多くは、プログラム命令をパンチ紙テープからメモリにロードしていました。各サブルーチンは、メインプログラム(または「メインライン」[16])の前または後にロードまたは接合された別のテープで提供され、同じサブルーチンテープをさまざまなプログラムで使用できました。同様のアプローチは、パンチカードからプログラム命令をロードするコンピュータでも使用されていました。サブルーチンライブラリという名前は、もともと文字通り、共同使用のためにテープまたはカードのデッキのインデックス付きコレクションを保持するライブラリを意味していました。
間接ジャンプで戻る
自己修正コードの必要性をなくすために、コンピュータ設計者は最終的に間接ジャンプ命令を提供しました。この命令のオペランドは、戻りアドレスそのものではなく、戻りアドレスを含む変数またはプロセッサ レジスタの場所でした。
これらのコンピュータでは、関数の戻りジャンプを変更する代わりに、呼び出しプログラムは戻りアドレスを変数に格納し、関数が完了したときに、事前定義された変数によって指定された場所に実行を誘導する間接ジャンプを実行します。
サブルーチンにジャンプ
もう 1 つの進歩は、サブルーチンへのジャンプ命令です。これは、戻りアドレスの保存と呼び出しジャンプを組み合わせることで、オーバーヘッドを大幅に最小限に抑えます。
たとえば、 IBM System/360では、プロシージャ呼び出し用に設計された分岐命令 BAL または BALR は、規則レジスタ 14 により、命令で指定されたプロセッサ レジスタに戻りアドレスを保存します。戻るには、サブルーチンはそのレジスタを介して間接分岐命令 (BR) を実行するだけで済みます。サブルーチンが他の目的 (別のサブルーチンの呼び出しなど) でそのレジスタを必要とする場合は、レジスタの内容をプライベート メモリ位置またはレジスタスタックに保存します。
HP 2100などのシステムでは、JSB 命令は同様のタスクを実行しますが、戻りアドレスが分岐先のメモリ位置に格納される点が異なります。プロシージャの実行は実際には次のメモリ位置から開始されます。HP 2100 アセンブリ言語では、たとえば次のように記述します。
...
JSB MYSUB (サブルーチン MYSUB を呼び出します。)
BB ... (MYSUB が完了したらここに戻ってきます。)
メインプログラムからMYSUBというサブルーチンを呼び出す。サブルーチンは次のように記述される。
MYSUB NOP (MYSUB の戻りアドレスの格納場所)
AA ... (MYSUB 本体の開始)
...
JMP MYSUB,I (呼び出し元のプログラムに戻ります。)
JSB 命令は、NEXT 命令のアドレス (つまり、BB) をそのオペランドとして指定された場所 (つまり、MYSUB) に配置し、その後 NEXT 場所に分岐します (つまり、AA = MYSUB + 1)。その後、サブルーチンは間接ジャンプ JMP MYSUB, I を実行してメイン プログラムに戻り、MYSUB の場所に格納されている場所に分岐します。
Fortran やその他の言語のコンパイラは、利用可能な場合はこれらの命令を簡単に利用できます。このアプローチは複数レベルの呼び出しをサポートしていましたが、サブルーチンの戻りアドレス、パラメータ、戻り値には固定のメモリ位置が割り当てられていたため、再帰呼び出しは許可されませんでした。
ちなみに、 1980 年代初頭にLotus 1-2-3でスプレッドシートの再計算の依存関係を検出するために同様の方法が使用されていました。つまり、各セルに戻りアドレスを格納するための場所が予約されていました。自然な再計算順序では循環参照が許可されないため、これにより、 IBM PCなどの小型コンピューターでは非常に制限されていたメモリ内のスタック用のスペースを予約せずにツリーをたどることができます。
コールスタック
関数呼び出しの最近の実装のほとんどは、スタック データ構造の特殊なケースである呼び出しスタックを使用して、関数呼び出しと戻りを実装します。各プロシージャ呼び出しは、スタックの最上部にスタック フレームと呼ばれる新しいエントリを作成します。プロシージャが戻ると、そのスタック フレームはスタックから削除され、そのスペースは他のプロシージャ呼び出しに使用できます。各スタック フレームには、対応する呼び出しのプライベート データが含まれます。これには通常、プロシージャのパラメーターと内部変数、および戻りアドレスが含まれます。
呼び出しシーケンスは、通常の命令のシーケンスによって実装できます (縮小命令セット コンピューティング(RISC) および超長命令語(VLIW) アーキテクチャで現在でも使用されているアプローチ)。ただし、1960 年代後半以降に設計された多くの従来のマシンには、その目的のための特別な命令が含まれています。
コール スタックは通常、連続したメモリ領域として実装されます。スタックの一番下がこの領域内の最低アドレスか最高アドレスかは設計上の選択であり、スタックがメモリ内で前方または後方に拡張される可能性がありますが、多くのアーキテクチャでは後者が選択されています。[引用が必要]
一部の設計、特に一部のForth実装では、2 つの別々のスタックが使用されていました。1 つは主に制御情報用 (戻りアドレスやループ カウンターなど)、もう 1 つはデータ用です。前者はコール スタックであるか、コール スタックのように動作し、プログラマーは他の言語構造を通じて間接的にしかアクセスできませんでしたが、後者はより直接的にアクセスできました。
スタックベースのプロシージャ呼び出しが最初に導入されたとき、貴重なメモリを節約することが重要な動機でした。[要出典]この方式では、コンパイラは各プロシージャのプライベート データ (パラメーター、戻りアドレス、およびローカル変数) 用にメモリ内に別の領域を予約する必要がありません。どの瞬間も、スタックには現在アクティブな呼び出し(つまり、呼び出されたがまだ戻っていない呼び出し) のプライベート データのみが含まれます。プログラムは通常ライブラリからアセンブルされるため、何千もの関数を含み、そのうちのほんの一握りの関数だけが特定の瞬間にアクティブであるプログラムは珍しくありませんでした (現在でもそうです)。[要出典]このようなプログラムでは、呼び出しスタック メカニズムによって大量のメモリを節約できます。実際、呼び出しスタック メカニズムは、自動メモリ管理の最も初期かつ最も単純な方法と見なすことができます。
ただし、コール スタック メソッドのもう 1 つの利点は、同じプロシージャへのネストされた各呼び出しでプライベート データの個別のインスタンスが取得されるため、 再帰関数呼び出しが可能になることです。
マルチスレッド環境では、通常、複数のスタックが存在します。[17]コルーチンや遅延評価を完全にサポートする環境では、スタック以外のデータ構造を使用してアクティベーションレコードを格納する場合があります。
遅延スタッキング
コール スタック メカニズムの欠点の 1 つは、プロシージャ呼び出しとそれに対応する戻り値のコストが増加することです。[説明が必要]追加コストには、スタック ポインターの増分と減分 (および、一部のアーキテクチャーでは、スタック オーバーフローのチェック)、および絶対アドレスではなくフレーム相対アドレスによるローカル変数とパラメーターへのアクセスが含まれます。このコストは、実行時間の増加、またはプロセッサの複雑さの増加、あるいはその両方として実現される可能性があります。
このオーバーヘッドは、リーフプロシージャまたはリーフ関数で最も顕著で、問題になります。これらは、プロシージャ呼び出しを行わずに戻ります。[18] [19] [20]このオーバーヘッドを減らすために、多くの最近のコンパイラは、コールスタックの使用を本当に必要になるまで遅らせようとします。[要出典]たとえば、プロシージャPの呼び出しでは、呼び出されたプロシージャの戻りアドレスとパラメータが特定のプロセッサレジスタに格納され、単純なジャンプによってプロシージャの本体に制御が移されます。プロシージャP が他の呼び出しを行わずに戻る場合、コールスタックはまったく使用されません。プロシージャP が別のプロシージャQを呼び出す必要がある場合は、コールスタックを使用して、 Q が戻った後に必要になるレジスタの内容 (戻りアドレスなど) を保存します。
特徴
一般的に、呼び出し可能ユニットは、最初の命令から始まり、内部ロジックによって指示された場合を除き、順番に実行される命令のリストです。プログラムの実行中に何度も呼び出すことができます。呼び出し命令が制御を返すと、呼び出し命令の次の命令から実行が続行されます。
実装
呼び出し可能なユニットの実装の機能は時間の経過とともに進化し、コンテキストによって異なります。このセクションでは、さまざまな一般的な実装の機能について説明します。
一般的な特徴
最近のプログラミング言語のほとんどは、次のような機能にアクセスするための 構文を含め、関数を定義して呼び出す機能を提供しています。
- 関数の実装をプログラムの残りの部分から区別する
- 関数に識別子、名前を割り当てる
- それぞれの名前とデータ型を持つ正式なパラメータを定義する
- 戻り値にデータ型を割り当てる(ある場合)
- 関数本体で戻り値を指定する
- 関数を呼び出す
- 呼び出された関数の仮パラメータに対応する実パラメータを提供する
- 呼び出し時点で制御を呼び出し元に返す
- 呼び出し元で戻り値を消費する
- 呼び出しによって返された値を破棄する
- 変数にプライベートな命名スコープを提供する
- 関数内でアクセス可能な関数外の変数を特定する
- 例外条件を関数外に伝播し、呼び出しコンテキストで処理する
- 関数をモジュール、ライブラリ、オブジェクト、クラスなどのコンテナにパッケージ化する
ネーミング
Pascal、Fortran、Ada 、およびBASICの多くの方言などの一部の言語では、値を返す呼び出し可能ユニット (関数またはサブプログラム)と値を返しますが返さない呼び出し可能ユニット (サブルーチンまたはプロシージャ) に対して異なる名前を使用します。C、C++、C#、Lispなどの他の言語では、呼び出し可能ユニットに対して関数という 1 つの名前のみを使用します。C ファミリの言語では、 キーワードを使用して戻り値がないことを示します。
void
呼び出し構文
値を返すように宣言されている場合、戻り値を使用するために呼び出しを式に埋め込むことができます。たとえば、平方根呼び出し可能ユニットは、 のように呼び出されますy = sqrt(x)
。
値を返さない呼び出し可能ユニットは、のようなスタンドアロンステートメントprint("hello")
として呼び出されます。この構文は、値を返す呼び出し可能ユニットにも使用できますが、戻り値は無視されます。
一部の古い言語では、 のように、戻り値を消費しない呼び出しにキーワードが必要ですCALL print("hello")
。
パラメータ
ほとんどの実装、特に最近の言語では、呼び出し可能オブジェクトが仮パラメータとして宣言するパラメータをサポートしています。呼び出し元は、一致する実際のパラメータ、つまり引数を渡します。プログラミング言語によって、引数を渡すための規則が異なります。
大会 | 説明 | 使用される場所 |
---|---|---|
価値によって | 引数のコピーが渡される | Algol 60以降のほとんどのAlgol系言語(Pascal、Delphi、Simula、CPL、PL/M、Modula、Oberon、Ada、その他C、C++、Javaなど) のデフォルト |
参照により | 引数への参照が渡されます。通常はそのアドレスです。 | Algol 60以降のほとんどの Algol 系言語(Algol 68、Pascal、Delphi、Simula、CPL、PL/M、Modula、Oberon、Ada、C++、Fortran、PL/I など) で 選択可能 |
結果によって | 呼び出し中に計算された値は戻り時に引数にコピーされます。 | Ada OUTパラメータ |
値の結果によって | 引数のコピーが渡され、呼び出し中に計算された値が戻り時に引数にコピーされます。 | Algol、Swift入出力パラメータ |
名前で | マクロのように、パラメータを未評価の引数式に置き換え、呼び出し可能オブジェクトがパラメータを使用するたびに、呼び出し元のコンテキストで引数を評価します。 | アルゴル、スカラ |
定数値 | パラメータが定数として扱われる点を除いて、値渡しと同様です。 | PL/I NONASSIGNABLEパラメータ、Ada INパラメータ |
戻り値
BASIC などの一部の言語では、値を返す呼び出し可能オブジェクトと返さない呼び出し可能オブジェクトで、呼び出し可能オブジェクトの構文 (キーワードなど) が異なります。他の言語では、構文はどちらでも同じです。これらの言語の一部では、戻り値がないことを宣言するために追加のキーワードが使用されます ( void
C、C++、C# など)。Python などの一部の言語では、違いは本体に値を持つ return ステートメントが含まれているかどうかであり、特定の呼び出し可能オブジェクトは、制御フローに基づいて値を返す場合と返さない場合があります。
副作用
多くのコンテキストでは、呼び出し可能オブジェクトには、渡されたデータやグローバル データの変更、周辺機器からの読み取りや周辺機器への書き込み、ファイルへのアクセス、プログラムやマシンの停止、プログラム実行の一時停止などの副作用動作がある場合があります。
設計原則の推進で知られるロバート・C・マーティンは、副作用は望ましくないと考えています。マーティンは、副作用によって時間的結合や順序依存性が生じる可能性があると主張しています。[21]
Haskellなどの厳密に関数的なプログラミング言語では、関数は副作用を持たないため、プログラムの状態を変更することはできません。関数は同じ入力に対して常に同じ結果を返します。このような言語では通常、値を返す関数のみがサポートされます。戻り値も副作用もない関数には価値がないためです。
ローカル変数
ほとんどのコンテキストは、ローカル変数をサポートしています。ローカル変数は、呼び出し可能オブジェクトが中間値を保持するために所有するメモリです。これらの変数は通常、戻りアドレスなどの他の情報とともに、呼び出しスタック上の呼び出しのアクティベーション レコードに格納されます。
ネストされた呼び出し – 再帰
言語でサポートされている場合、呼び出し可能オブジェクトは自身を呼び出すことができ、同じ呼び出し可能オブジェクトの別のネストされた実行が実行される間、自身の実行が一時停止されます。再帰は、複雑なアルゴリズムを簡素化し、複雑な問題を分解するのに役立つ手段です。再帰言語は、各呼び出しでローカル変数の新しいコピーを提供します。プログラマーが再帰呼び出し可能オブジェクトでローカル変数を使用する代わりに同じ変数を使用することを望む場合、通常は静的またはグローバルなどの共有コンテキストでそれらを宣言します。
ALGOL、PL/I、Cに遡る言語や現代の言語では、ほとんどの場合、各呼び出しのアクティベーション レコードを提供するために命令セットによってサポートされている呼び出しスタックが使用されます。これにより、ネストされた呼び出しは、中断された呼び出し変数に影響を与えることなく、ローカル変数を変更できます。
再帰により、数学的帰納法と再帰分割統治アルゴリズムによって定義された機能を直接実装できます。以下は、フィボナッチ数列を見つけるための C/C++ の再帰関数の例です。
int Fib ( int n ) { if ( n <= 1 ) { return n ; } return Fib ( n - 1 ) + Fib ( n - 2 ); }
Fortranのような初期の言語は、各呼び出し可能オブジェクトに対して1組の変数と戻りアドレスしか割り当てられなかったため、当初は再帰をサポートしていませんでした。[22]初期のコンピュータ命令セットでは、戻りアドレスと変数をスタックに格納することが困難でした。インデックスレジスタまたは汎用レジスタを備えたマシン(CDC 6000シリーズ、PDP-6、GE 635、System/360、UNIVAC 1100シリーズなど)は、それらのレジスタの1つをスタックポインタとして使用できました。
ネストされたスコープ
Ada、Pascal、PL/I、Python などの一部の言語では、関数本体内で関数を宣言および定義することがサポートされており、内部の名前は外部の本体内でのみ表示されます。
再入性
同じ呼び出し可能オブジェクトの別の実行がすでに進行中であっても、呼び出し可能オブジェクトが適切に実行できる場合、その呼び出し可能オブジェクトは再入可能と呼ばれます。再入可能な呼び出し可能オブジェクトは、複数のスレッドが互いに干渉する心配なく同じ呼び出し可能オブジェクトを呼び出すことができるため、マルチスレッドの状況でも役立ちます。IBM CICS トランザクション処理システムでは、準再入可能は、多くのスレッドによって共有されるアプリケーション プログラムに対する、 若干制限の少ない、しかし同様の要件でした。
オーバーロード
一部の言語ではオーバーロードがサポートされています。つまり、同じスコープ内で同じ名前の複数の呼び出し可能オブジェクトを許可しますが、異なるタイプの入力に対して動作します。実数、複素数、行列入力に適用される平方根関数を考えてみましょう。入力のタイプごとにアルゴリズムが異なり、戻り値の型も異なる場合があります。同じ名前 (つまりsqrt)を持つ 3 つの別々の呼び出し可能オブジェクトを記述すると、結果のコードは記述しやすく、保守しやすくなります。これは、それぞれに比較的理解しやすく覚えやすい名前が付けられるため、 sqrt_real、sqrt_complex、qrt_matrixなどの長くて複雑な名前を付ける必要がなくなるためです。
オーバーロードは、強い型付けをサポートする多くの言語でサポートされています。多くの場合、コンパイラは入力引数の型に基づいて呼び出すオーバーロードを選択します。または、入力引数がオーバーロードを選択しない場合は失敗します。古い言語や弱い型付けの言語では、通常、オーバーロードはサポートされていません。
以下は、異なる型を受け入れる
2 つの関数のC++でのオーバーロードの例です。Area
// 高さと幅で定義される四角形の面積を返します
double Area ( double h , double w ) { return h * w ; }
// 半径で定義される円の面積を返します
double Area ( double r ) { return r * r * 3.14 ; }
int main () { double rectangle_area = Area ( 3 , 4 ); double circle_area = Area ( 5 ); }
PL/I には、GENERIC
異なるタイプの引数で呼び出されるエントリ参照のセットの総称名を定義する属性があります。例:
gen_name GENERIC(を宣言する 名前 WHEN(FIXED BINARY)、 炎 WHEN(FLOAT)、 パス名 OTHERWISE);
各エントリには複数の引数定義を指定できます。引数が FIXED BINARY の場合は「gen_name」の呼び出しによって「name」が呼び出され、「FLOAT」の場合は「flame」が呼び出されます。引数がいずれの選択肢にも一致しない場合は、「pathname」が呼び出されます。
閉鎖
クロージャは、呼び出し可能オブジェクトと、それが作成された環境からキャプチャされた変数の値を組み合わせたものです。クロージャは、John McCarthyによって導入された Lisp プログラミング言語の注目すべき機能です。実装によっては、クロージャが副作用のメカニズムとして機能することがあります。
例外レポート
ハッピー パスの動作に加えて、呼び出し可能オブジェクトは、実行中に発生した 例外的な状態について呼び出し元に通知する必要がある場合があります。
最近の言語のほとんどは例外をサポートしており、例外ハンドラが条件を処理するまで呼び出しスタックをポップする例外制御フローを可能にします。
例外をサポートしない言語では、戻り値を使用して呼び出しの成功または失敗を示すことができます。別の方法としては、成功を示すためにグローバル変数などのよく知られた場所を使用する方法があります。呼び出し可能オブジェクトは値を書き込み、呼び出し元は呼び出し後にその値を読み取ります。
IBM System/360では、サブルーチンから戻りコードが返されることが予想されていましたが、戻り値は 4 の倍数になるよう設計されることが多かったため、呼び出し命令の直後に配置されることが多い分岐テーブルへの直接分岐テーブルインデックスとして使用して余分な条件テストを回避し、さらに効率性を向上させることができました。System /360 アセンブリ言語では、たとえば次のように記述します。
BAL 14、SUBRTN01はサブルーチンへ行き、戻りアドレスをR14に格納する
B TABLE(15) は、reg 15の戻り値を使用してブランチテーブルをインデックスします。
* 適切なブランチ命令に分岐します。
表B OK 戻りコード =00 良好 }
B 不正な戻りコード =04 無効な入力 } 分岐テーブル
B エラー戻りコード =08 予期しない状態 }
オーバーヘッドを呼び出す
呼び出しには実行時のオーバーヘッドがあり、これには以下が含まれますが、これらに限定されません。
- コールスタックストレージの割り当てと再利用
- プロセッサレジスタの保存と復元
- 入力変数のコピー
- 呼び出し後の値を呼び出し元のコンテキストにコピーする
- 戻りコードの自動テスト
- 例外の処理
- オブジェクト指向言語における仮想メソッドなどのディスパッチ
呼び出しの実行時コストを最小限に抑えるためにさまざまな手法が採用されています。
コンパイラの最適化
呼び出しのオーバーヘッドを最小限に抑える最適化の中には、簡単に思えるものもありますが、呼び出し可能オブジェクトに副作用がある場合には使用できません。たとえば、式 では(f(x)-1)/(f(x)+1)
、2 回の呼び出しで異なる結果が返される可能性があるため、関数を1f
回だけ呼び出してその値を 2 回使用することはできません。さらに、除算演算子のオペランドの評価順序を定義する言語はごくわずかですが、 の値は、x
最初の呼び出しで変更されている可能性があるため、2 回目の呼び出しの前に再度取得する必要があります。呼び出し可能オブジェクトに副作用があるかどうかを判断するのは難しく、実際にはライスの定理により決定不可能です。そのため、この最適化は純粋に関数型のプログラミング言語では安全ですが、関数型に限定されない言語のコンパイラは通常、最悪のケース、つまりすべての呼び出し可能オブジェクトに副作用がある可能性があると想定します。
インライン化
インライン化により、特定の呼び出し可能オブジェクトの呼び出しが排除されます。コンパイラは、各呼び出しを呼び出し可能オブジェクトのコンパイル済みコードに置き換えます。これにより、呼び出しのオーバーヘッドが回避されるだけでなく、その呼び出しのコンテキストと引数を考慮して、コンパイラが呼び出し元のコードをより効果的に最適化できるようになります。ただし、インライン化により、1 回しか呼び出されない場合や、本体が 1 行のように非常に短い場合を除き、コンパイル済みコードのサイズが大きくなるのが一般的です。
共有
呼び出し可能オブジェクトは、プログラム内で定義することも、複数のプログラムで使用できる ライブラリ内で個別に定義することもできます。
相互運用性
コンパイラは、明確に定義された呼び出し規約に従って、呼び出し文と戻り文を機械語の命令に変換します。同じコンパイラまたは互換性のあるコンパイラでコンパイルされたコードの場合、関数は、それを呼び出すプログラムとは別にコンパイルできます。呼び出し文と戻り文に対応する命令シーケンスは、プロシージャのプロローグとエピローグと呼ばれます。
組み込み関数
組み込み関数(builtin function)または組み込み関数( intrinsic function)は、コンパイラがコンパイル時にコードを生成したり、他の関数とは異なる方法で提供したりする関数です。[23]組み込み関数はプログラミング言語に組み込まれているため、他の関数のように定義する必要はありません。 [24]
プログラミング
トレードオフ
利点
プログラムを関数に分割する利点は次のとおりです。
- 複雑なプログラミングタスクをより単純なステップに分解する。これは、データ構造とともに構造化プログラミングの2つの主要なツールの1つです。
- プログラム内の重複コードを削減する
- 複数のプログラム間でのコードの再利用を可能にする
- 大規模なプログラミングタスクを複数のプログラマーまたはプロジェクトのさまざまな段階に分割する
- 関数のユーザーから実装の詳細を隠す
- コード ブロックを関数呼び出しに置き換えて、コード ブロックを説明する関数名を使用することで、コードの読みやすさが向上します。これにより、関数が再利用されない場合でも、呼び出しコードが簡潔で読みやすくなります。
- トレーサビリティの向上(つまり、ほとんどの言語では、関連する関数の名前や、ファイル名や行番号などの詳細情報を含む呼び出しトレースを取得する方法を提供しています)。コードを関数に分解しないと、デバッグが著しく損なわれます。
デメリット
インライン コードを使用する場合と比較して、関数を呼び出すと、呼び出しメカニズムに計算オーバーヘッドが発生します。 [引用が必要]
関数には通常、関数の入口と出口の両方で標準的なハウスキーピングコードが必要です (関数のプロローグとエピローグ。通常は、汎用レジスタと戻りアドレスを最低限保存します)。
コンベンション
呼び出し可能オブジェクトに関して、多くのプログラミング規則が開発されてきました。
命名に関しては、多くの開発者は、呼び出し可能オブジェクトが特定のタスクを実行する場合は動詞で始まるフレーズで、問い合わせを行う場合は形容詞で、変数を置き換えるために使用される場合は名詞で始まるフレーズで、呼び出し可能オブジェクトの名前を付けます。
一部のプログラマーは、呼び出し可能オブジェクトは 1 つのタスクだけを実行する必要があり、複数のタスクを実行する場合は複数の呼び出し可能オブジェクトに分割する必要があることを提案しています。彼らは、呼び出し可能オブジェクトはソフトウェア メンテナンスの重要なコンポーネントであり、プログラム内での役割は明確に区別する必要があると主張しています。
モジュラー プログラミングの支持者は、各呼び出し可能オブジェクトがコードベースの残りの部分に対して最小限の依存性を持つべきだと主張します。たとえば、グローバル変数の使用は、グローバル変数を使用するすべての呼び出し可能オブジェクト間の結合を追加するため、一般的には賢明ではないと考えられています。このような結合が不要な場合は、渡されたパラメーターを受け入れるように呼び出し可能オブジェクトをリファクタリングすることを推奨しています。
例
初期のBASIC
初期の BASIC バリアントでは、各行に実行順序を付ける一意の番号 (行番号GOSUB
) が必要で、呼び出し可能なコードの分離、引数の受け渡しや値の返送のメカニズムはなく、すべての変数はグローバルです。 コマンドが提供されます。ここで、sub は、サブ プロシージャ、サブプロシージャ、またはサブルーチンの略です。制御は指定された行番号にジャンプし、戻ると次の行に進みます。
10 REM 基本プログラム20 GOSUB 100 30 GOTO 20 100 INPUT “ GIVE ME A NUMBER ” ; N 110 PRINT “ THE SQUARE ROOT OF ” ; N ; 120 PRINT “ IS ” ; SQRT ( N ) 130 RETURN
このコードは、ユーザーに数値の入力を繰り返し求め、その値の平方根を報告します。100 行目から 130 行目は呼び出し可能です。
スモールベーシック
テキストベースの言語でプログラミングを初めて学ぶ学生を対象としたMicrosoft Small Basicでは、呼び出し可能なユニットはサブルーチンと呼ばれます。Sub
キーワードはサブルーチンの開始を示し、その後に名前識別子が続きます。後続の行はキーワードで終わる本体ですEndSub
。
[25]
サブSayHello TextWindow 。WriteLine ( "こんにちは!" ) EndSub
これを と呼ぶこともできるSayHello()
。
[26]
ビジュアルベーシック
最新の製品ラインやVB6を含むVisual Basic(VB)の後のバージョンでは、呼び出し可能なユニットの概念を表すためにプロシージャという用語が使用されています。キーワードは、値を返さないか、値を返すために使用されます。クラスのコンテキストで使用される場合、プロシージャはメソッドです。
[27]Sub
Function
各パラメータには指定可能なデータ型がありますが、指定されていない場合は、 .NETObject
ベースの新しいバージョンではデフォルトで、VB6ではバリアントになります。[28]
VB は、それぞれキーワード と を介して、値と参照によるパラメータ渡し規則をサポートしています。が指定されていない限り、引数は に渡されます。したがって、が明示的に指定されることはほとんどありません。
ByVal
ByRef
ByRef
ByVal
ByVal
数値のような単純な型の場合、これらの規則は比較的明確です。渡すと、ByRef
プロシージャは渡された変数を変更できますが、渡すとByVal
変更できません。オブジェクトの場合、オブジェクトは常に参照として扱われるため、セマンティクスによってプログラマが混乱する可能性があります。オブジェクトを渡すとByVal
、オブジェクトの状態ではなく参照がコピーされます。呼び出されたプロシージャは、そのメソッドを介してオブジェクトの状態を変更できますが、実際のパラメータのオブジェクト参照を変更することはできません。
Sub DoSomething () ' ここにコードを書くEnd Sub
は値を返さないので、次のようにスタンドアロンで呼び出す必要があります。DoSomething
関数GiveMeFive ( )整数GiveMeFive = 5終了関数
これは値5を返し、呼び出しは次のような式の一部となる。y = x + GiveMeFive()
Sub AddTwo ( ByRef intValue as Integer ) intValue = intValue + 2 End Sub
これには副作用があり、参照によって渡された変数を変更し、v
のような変数に対して呼び出すことができますAddTwo(v)
。呼び出し前に v に 5 を指定すると、呼び出し後は 7 になります。
C および C++
CおよびC++では、呼び出し可能なユニットは関数と呼ばれます。関数定義は、関数が返す値の型の名前、またはvoid
関数が値を返さないことを示す名前で始まります。その後に関数名、括弧で囲んだ仮引数、中括弧で囲んだ本体行が続きます。
C++では、クラス内で非静的として宣言された関数はメンバー関数またはメソッドと呼ばれます。クラス外の関数はメンバー関数と区別するために フリー関数と呼ばれることがあります。 [29]
void doSomething () { /* コード */ }
この関数は値を返さず、常にスタンドアロンとして呼び出されます。doSomething()
int giveMeFive () {戻り値5 ; }
この関数は整数値5を返します。呼び出しはスタンドアロンでも、次のような式でも使用できます。y = x + giveMeFive()
void addTwo ( int * pi ) { * pi += 2 ; }
この関数には副作用があり、アドレスで渡された値を入力値に 2 を加えた値に変更します。変数に対して呼び出すこともできますv
。addTwo(&v)
この場合、アンパサンド (&) はコンパイラに変数のアドレスを渡すように指示します。呼び出し前に v に 5 を指定すると、呼び出し後は 7 になります。
void addTwo ( int & i ) { i += 2 ; }
この関数には C++ が必要です。C としてコンパイルされません。前の例と同じ動作ですが、実際のパラメータをアドレスではなく参照で渡します。 などの呼び出しにはaddTwo(v)
アンパサンドが含まれません。これは、コンパイラが呼び出しで構文なしで参照渡しを処理するためです。
英語
PL/Iでは、呼び出されたプロシージャに、文字列の長さや配列の境界などの引数に関する情報を提供する記述子が渡されることがあります。これにより、プロシージャはより汎用的になり、プログラマがそのような情報を渡す必要がなくなります。デフォルトでは、PL/I は引数を参照によって渡します。2 次元配列の各要素の符号を変更する (単純な) 関数は次のようになります。
change_sign: プロシージャ(配列); 配列(*,*) floatを宣言します。 配列 = -配列; change_sign の終了;
これは、次のようにさまざまな配列で呼び出すことができます。
/* 最初の配列の範囲は -5 から +10 および 3 から 9 です */ 配列1 (-5:10, 3:9)floatを宣言します。 /* 2 番目の配列の境界は 1 から 16 までと 1 から 16 まで */ 配列2 (16,16) floatを宣言します。 change_sign(array1)を呼び出します。 change_sign(array2)を呼び出します。
パイソン
Pythonでは、キーワードはdef
関数定義の開始を示します。関数本体のステートメントは、後続の行にインデントされて続き、最初の行またはファイルの終わりと同じようにインデントされた行で終了します。[30]
def format_greeting ( name ):
return "Welcome " + name
def greeting_martin ( ):
print ( format_greeting ( "Martin" ))
最初の関数は、呼び出し元から渡された名前を含む挨拶テキストを返します。2 番目の関数は最初の関数を呼び出し、greet_martin()
コンソールに「Welcome Martin」と書き込むように呼び出されます。
プロローグ
論理プログラムの手続き的解釈では、論理的含意は目標削減手続きとして動作します。次の形式の 規則(または節) は次のとおりです。
A :- B
論理的には以下のようになります。
A if B
は、と 統合するA
目標を のインスタンスであるサブ目標に縮小する手順として動作しますB
。
たとえば、次の Prolog プログラムを考えてみましょう。
mother_child (エリザベス、 チャールズ)。
father_child (チャールズ、 ウィリアム)。
father_child (チャールズ、 ハリー)。
parent_child ( X 、 Y ) :- mother_child ( X 、 Y )。
parent_child ( X 、 Y ) :- father_child ( X 、 Y )。
リレーショナル データベースの場合と同様に、母性機能はリレーションによって表現されることに注意してください。ただし、Prolog 内のリレーションは呼び出し可能な単位として
機能します。X = mother(Y)
たとえば、プロシージャ呼び出しは 出力 を生成します。ただし、同じプロシージャを他の入出力パターンで呼び出すこともできます。例:
?- parent_child(X, charles)
X = elizabeth
?- parent_child (エリザベス、 Y )
。Y = チャールズ。
?- parent_child ( X 、 Y )
。X =エリザベス 、Y =チャールズ。
X = チャールズ、
Y = ハリー。
X = チャールズ、
Y = ウィリアム。
?- 親子(ウィリアム、 ハリー)。
いいえ。
?- parent_child (エリザベス、 チャールズ)。
はい。
参照
- 非同期手続き呼び出し、他のアクティビティによってパラメータが設定された後に呼び出されるサブプログラム
- コマンド・クエリ分離(CQS)
- 複合操作
- コルーチン、つまり、両方がメインプログラムであるかのように互いを呼び出すサブプログラム
- 評価戦略
- イベントハンドラ、入力イベントまたは割り込みに応じて呼び出されるサブプログラム
- 関数(数学)
- 関数型プログラミング
- 融合操作
- 固有機能
- ラムダ関数(コンピュータプログラミング)、識別子にバインドされていない関数
- 論理プログラミング
- モジュールプログラミング
- 演算子のオーバーロード
- 保護された手順
- トランスクルージョン
参考文献
- ^ 「用語集」。nist.gov。NIST。2024年2月9日閲覧。
呼び出し可能なユニット: (ソフトウェアプログラムまたは論理設計の) モジュール内に表示される関数、メソッド、操作、サブルーチン、手順、または類似の構造ユニット。
- ^ ドナルド・E・クヌース(1997)。 『コンピュータプログラミングの技法、第1巻:基礎アルゴリズム』。アディソン・ウェズリー。ISBN 0-201-89683-4。
- ^ O.-J. Dahl、EW Dijkstra、CAR Hoare (1972)。構造化プログラミング。Academic Press。ISBN 0-12-200550-3。
- ^ ab Mauchly, JW (1982). 「EDVAC 型マシンの問題の準備」 Randell, Brian (編) 『デジタル コンピュータの起源』 Springer. pp. 393–397. doi :10.1007/978-3-642-61812-3_31. ISBN 978-3-642-61814-7。
- ^ Wheeler, DJ (1952). 「プログラムにおけるサブルーチンの使用」(PDF) . 1952 ACM 全国会議 (ピッツバーグ) の議事録 - ACM '52 . p. 235. doi : 10.1145/609784.609816 .
- ^ Wilkes, MV; Wheeler, DJ; Gill, S. (1951). 「電子デジタルコンピュータ用プログラムの作成」 . Addison-Wesley.
- ^ Dainith, John (2004). 「オープンサブルーチン」 コンピューティング辞典. Encyclopedia.com . 2013年1月14日閲覧。
- ^ チューリング、アラン・M. (1945)、AMチューリング博士による自動計算エンジン(ACE)の開発提案に関する報告書: 1946年2月にNPL執行委員会に提出Copeland, BJ編 (2005) 『Alan Turing's Automatic Computing Engine』 オックスフォード: オックスフォード大学出版局、p. 383に再録。ISBN 0-19-856593-3。
- ^ チューリング、アラン・マシソン(1946年3月19日)[1945]、自動計算エンジン(ACE)の数学部門の開発提案(注: 1946 年 3 月 19 日に英国国立物理学研究所の執行委員会で発表されました。)
- ^ カーペンター、ブライアン・エドワード、ドラン、ロバート・ウィリアム(1977年1月1日)[1975年10月]。「もう一つのチューリングマシン」。コンピュータジャーナル。20(3):269–279。doi : 10.1093/ comjnl /20.3.269。(11ページ)
- ^ ab Isaacson, Walter (2014年9月18日). 「Walter Isaacson on the Women of ENIAC」. Fortune . 2018年12月12日時点のオリジナルよりアーカイブ。 2018年12月14日閲覧。
- ^ Herman H. Goldstine、John von Neumann (1947)。「パート II、第 I-3 巻、電子計算機器の問題の計画とコーディング」(PDF)。電子計算機器の数学的および論理的側面に関するレポート(技術レポート)。(関連ページはPDFの163ページを参照)
- ^ Burroughs B5000 用プロセッサの動作特性(PDF)。改訂版 A。Burroughs Corporation。1963 年 5000-21005 。2024年2 月 8 日に閲覧。
- ^ 「プッシュダウン命令」(PDF) . プログラム型データプロセッサ6 - ハンドブック(PDF) . p. 37. 2024年2月8日閲覧。
- ^ Guy Lewis Steele Jr. AI メモ443。「「高価なプロシージャ コール」神話の打破、またはプロシージャ コールの実装は有害であると考えられる」セクション「C. プロシージャ コールの評判が悪い理由」。
- ^ Frank, Thomas S. (1983). PDP-11 とそのアセンブリ言語の紹介。Prentice-Hall ソフトウェア シリーズ。Prentice-Hall。p. 195。ISBN
9780134917047. 2016 年7 月 6 日に取得。
有用なサブルーチンのソース コードのコピーを組み立て担当者に提供し、メインライン プログラムを組み立て担当者に提示するときに、メインラインでどのサブルーチンが呼び出されるかを伝えることができます [...]
- ^ Buttlar, Dick; Farrell, Jacqueline; Nichols, Bradford (1996)。PThreads プログラミング: より優れたマルチプロセッシングのための POSIX 標準。「O'Reilly Media, Inc.」。pp. 2–5。ISBN 978-1-4493-6475-5. OCLC 1036778036.
- ^ 「ARM 情報センター」。Infocenter.arm.com。2013年9 月 29 日閲覧。
- ^ 「x64 スタックの使用法」。Microsoft Docs。Microsoft。2019年8 月 5 日閲覧。
- ^ 「関数型」。Msdn.microsoft.com。2013年9 月 29 日閲覧。
- ^ Martin, Robert C. ( 2008年8月 1 日)。Clean Code: A Handbook of Agile Software Craftsmanship (第 1 版) 。Pearson。ISBN 9780132350884. 2024年5月19日閲覧。
- ^ Verhoeff, Tom (2018). 「再帰のマスタークラス」。Böckenhauer, Hans-Joachim、Komm, Dennis、Unger, Walter (編)。「低地と高地の間の冒険: ユライ・フロムコヴィッチの60歳の誕生日を記念したエッセイ」。Springer。616ページ。ISBN 978-3-319-98355-4. OCLC 1050567095.
- ^ 「組み込み関数」. ibm.com . 2017年3月9日. 2023年12月25日閲覧。
- ^ Study Material Python. 2023年4月. p. 87 . 2023年12月25日閲覧。
- ^ 「Small Basic」. Small Basic . 2024年2月8日閲覧。
- ^ 「Small Basic 入門ガイド: 第 9 章: サブルーチン」。Microsoft。2024 年 1 月 17 日。
- ^ 「Visual Basic の手順」。Microsoft Learn。2021年 9 月 15 日。2024 年2 月 8 日に閲覧。
- ^ 「Dim ステートメント (Visual Basic)」。Microsoft Learn。2021年 9 月 15 日。2024 年2 月 8 日に閲覧。
- ^ 「自由関数とは何を意味するのか」。
- ^ 「4. その他の制御フロー ツール — Python 3.9.7 ドキュメント」。