コンテンツにスキップ

「手続き型プログラミング」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
m "Template:" を含むテンプレート呼び出し
(同じ利用者による、間の5版が非表示)
1行目: 1行目:
{{独自研究|date=2016年6月}}
{{独自研究|date=2016年6月}}
{{出典の明記|date=2016年6月}}
{{出典の明記|date=2016年6月}}
[[ファイル:Процесс решения задачи проектирования.png|境界|右|フレームなし|200x200ピクセル]]
[[ファイル:Процесс решения задачи проектирования.png|境界|右|フレームなし|265x265px]]
{{プログラミング・パラダイム}}
{{プログラミング・パラダイム}}
'''手続き型プログラミング'''(てつづきがたプログラミング、{{lang-en-short|''Procedural programming''}})は、手続きの定義と呼び出しをプログラム全体を組み立てる土台にした[[プログラミングパラダイム]]である。手続きは言語によってサブルーチン、関数、サブプログラムとも呼ばれている。手続きはプログラム全体を区分けした部分プログラムでもあり、一定量計算ステップまたは命令コードのまとまりを任意の定義に結び付けて識別化したコードユニットである。手続き型プログラミング[[命令型プログミング]]の分類属しおり1958年の[[FORTRAN|FORTRANⅡ]][[ALGOL]][[COBOL]]とった最初期の[[高水準言語]]から導入されている。
'''手続き型プログラミング'''(てつづきがたプログラミング、{{lang-en-short|''Procedural programming''}})は、手続き(''procedure'')の定義と呼び出しをプログラムを組み立てる基礎にした[[プログラミングパラダイム]]である。手続きは言語によってサブルーチン、関数、サブプログラムとも呼ばれており、プログラムの命令コードのまとまりを任意の手続きで抽象化したコードユニットである。手続きは入力されたパメータ引数よっ処理内容を変化させ処理結果となるリターン値を出力する事ができる。パラメータ引数リターン値その双方を持たな手続き定義できる。


手続きは一般的に[[非決定性有限オートマトン]]に準拠しているので、入力値による処理内容とそこからの出力値は、その手続き枠外の外部環境状態によっても変化する。この遷移図の可変性を指して手続き的(''procedual'')とも言われる。1958年の[[FORTRAN|FORTRANⅡ]]、[[ALGOL]]、[[COBOL]]といった最も初期の[[高水準言語]]から導入されている。
[[プロシージャ|手続き]](''procedure'')はプログラム内のあらゆるポイントから呼び出す(''call'')ことができる。手続き内の終端位置に達した時は、その呼び出しポイントの次の命令コードに復帰(''return'')される。途中位置からでも復帰できる。手続きの呼び出しと復帰はコンピュータ側が提供する[[コールスタック]]機能によって実現されている。


== 特徴 ==
== 特徴 ==
手続き型プログラミングでは基本的に、起点になるメインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きから参照可能なグローバル変数集合といったプログラム構成になる。複数の手続きから参照されるあらゆるデータをグローバルにまとめてしまう簡素な設計は、プログラム全体への理解をむしろ促進するものとして小中規模のソフトウェア開発には適したものとされている。
手続き型プログラミングでは基本的に、起点になるメインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きからアクセス可能なグローバル変数集合といったプログラム構成になる。複数の手続きからアクセスされるあらゆるデータをグローバルにまとめてしまう簡素な設計は、プログラム全体への理解をむしろ促進するものとして小中規模のソフトウェア開発には適したものとされている。


=== 手続きとは ===
=== 手続きとは ===
[[手続き]](''procedure'')は、命令コード(''instruction code'')のまとまりをパラメータリスト付きの識別子に結び付けたコードユニットである。プログラム起点のメインルーチンは手続きとは見なされない。識別子がリターン代入対象になる。れは言語共通の見解である。[[COBOL]]では副プログラムが手続き相当であり、副プログラムの命令コード記述部分を手続き部と呼んだ。[[FORTRAN]]ではリターン値を持たない方の手続きをサブルーチン、持つ方の手続きを関数と呼んだ。[[ALGOL]]ではどちらも手続きと呼び、[[C言語]]ではどちらも関数と呼んだ。[[Pascal]]ではリターン値を持たない方を手続き、持つ方を関数とした。[[BASIC]]ではリターン値無しをサブプログラム、有りを関数と呼んだ。なお、FORTRANのサブルーチン&関数はその内部サブルーチン&内部関数を複数定義可能でそれらをまとめたものを外部手続き(邦訳は外部副プログラム)と定義している。
[[手続き]](''procedure'')は、命令コード(''instruction code'')のまとまりをパラメータリスト付きの識別子に結び付けたコードユニットである。識別子は同時にリターン値の代入対象になる。命令コードの一行単位はステートメント(''statement'')と呼ばれる。プログラム起点のメインルーチンは手続きとは見なされない。[[プロシージャ|手続き]]はプログラム内のあらゆるポイントから呼び出すことできる。手続き内の終端位置に達した時は、その呼び出しポイントの次の命令コードにリターンされる。手続き内途中位置からでもリターンできる。手続きの呼び出しとリターンは、一般にコンピュータ側が提供する[[コールスタック]]機能によって実現さている。
手続きの名称は言語によって異なっている。[[COBOL]]では副プログラムが手続き相当であり、副プログラムの命令コード記述部分を手続き部と呼んだ。[[FORTRAN]]ではリターン値を持たない方の手続きをサブルーチン、持つ方の手続きを関数と呼んだ。[[ALGOL]]ではどちらも手続きと呼び、[[C言語]]ではどちらも関数と呼んだ。[[Pascal]]ではリターン値を持たない方を手続き、持つ方を関数とした。[[BASIC]]ではリターン値無しをサブプログラム、有りを関数と呼んだ。なお、FORTRANのサブルーチン&関数はその内部サブルーチン&内部関数を複数定義可能でそれらをまとめたものを外部手続き(邦訳は外部副プログラム)と定義している。


手続きの識別子は手続き名と呼ばれる。パラメータリストには任意の個数の引数が列挙される。引数無しのケースもある。手続き名+パラメータリストの次にコードブロックが置かれる。コードブロックには1行以上の命令コードが列記される。命令コードは引数の使用によってプロセスの多相性を実現できる。コードブロックは[[レキシカルスコープ]]の範囲になり、そのスコープ専用のローカル変数を定義できる。ローカル変数は静的変数と自動変数に分かれる。静的変数はプログラム実行中を通して代入値が保持される。自動変数は[[スタックフレーム]]を利用したもので、手続き内へのエントリと共に自動確保され、リターンと共に自動消去されるものである。命令コード行の終端に達するとリターンする。リターンとはその手続きの呼び出しポイントの次の命令行に移動することである。プロセスの結果を示すリターン値は手続きの識別子を媒体にして呼び出し元に渡される。リターン命令で途中位置からのリターンもできる。スタックフレームの利用により手続きは[[再帰呼び出し]]も可能である。
手続きの識別子は手続き名と呼ばれる。パラメータリストには任意の個数の引数が列挙される。引数無しのケースもある。手続き名+パラメータリストの次にコードブロックが置かれる。コードブロックには1行以上の命令コードが列記される。命令コードは引数の使用によってプロセスの多相性を実現できる。コードブロックは[[レキシカルスコープ]]の範囲になり、そのスコープ専用のローカル変数を定義できる。ローカル変数は静的変数と自動変数に分かれる。静的変数はプログラム実行中を通して代入値が保持される。自動変数は[[スタックフレーム]]を利用したもので、手続き内へのエントリと共に自動確保され、リターンと共に自動消去されるものである。命令コード行の終端に達するとリターンする。リターンとはその手続きの呼び出しポイントの次の命令行に移動することである。プロセスの結果を示すリターン値は手続きの識別子を媒体にして呼び出し元に渡される。リターン命令で途中位置からのリターンもできる。スタックフレームの利用により手続きは[[再帰呼び出し]]も可能である。


=== 手続き型プログラムの構 ===
=== 手続き型プログラムの構 ===
手続き型プログラムは、メインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きから参照可能なグローバル変数集合によって構成される。プログラムのコードは手続き単位に分割される。プログラムのデータはまずグローバル変数とローカル変数に大別される。ローカル変数は各手続きの[[スコープ]]内に分散配置されてその手続き専用になる。グローバル変数スコープには、複数の手続きから参照される様々な変数が雑然と置かれることになる。手続き型言語では、特定のグローバル変数を特定の手続きグループ専用にするといった設計はプログラマの注意力に委ねられた。どの手続きから参照され、またどの手続きから変更されるかの把握が難しいグローバル変数はバグの温床になり得ものであった。
手続き型プログラムは、メインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きからアクセス可能なグローバル変数集合によって構成される。プログラムのコードは手続き単位に分割される。プログラムのデータはグローバル変数とローカル変数に大別される。ローカル変数は各手続きの[[スコープ]]内に分散配置されてその手続き専用になる。グローバル変数スコープには、複数の手続きからアクセスされる様々な変数が雑然と置かれることになる。したがって特定のグローバル変数を特定の手続きグループ専用にするといった設計はプログラマの注意力に委ねられた。どの手続きから参照されており、またどの手続きから変更されているかの把握が難しいグローバル変数はバグの温床になることが多かた。これはグローバル変数問題などと呼ばれて1960年代前半には指摘されるようになっていた。


=== モジュール機能 ===
の解決のために任意の手続きグループ変数グループをまとめて形式化された[[モジュール]]として定義できる機能が誕生し、それ準拠したパラダイムはモジューラプログラミング(''modular programming'')と呼ばれた。これは手続き型プログラミングの最も身近な発展形である。[[Modula-2]]がその先例となった。グローバルスコープとローカルスコープの間にモジュールスコープが追加され、グロ公開された手続きと変数以外はモジュールスコープに隠蔽する事ができた。こ機能は「情報隠蔽」と呼ばれる。また手続きと構造体の実装部分を隠蔽して定義部分だけをグローバル公開する機能は「抽象化」と呼ばれた。手続き定義部分は、返値型+手続き名+パラメタリストであり[[関数プロトタイプ]]と同義であ。構造体定義部とはタグ名を指す。抽象化さた構造体同じモジュールの手続きのための引数用途限定になり、フィールドには当然アクセスできな
グローバル変数問題の解決のために任意の手続きグループ変数グループをまとめて[[モジュール]]として定義できる機能が誕生した。この機能を中心にしたパラダイムは{{仮リンク|モジューラプログラミング|en|modular programming}}と呼ばれた。これは手続き型プログラミングの最も身近な発展形である。グローバルスコープとローカルスコープの間にモジュールスコープが追加され、モジュール内アクセス限定の手続きと変数を定義できるようになった。モジュール内の手続きと変数は、グローバル公開するのとモジュル内限定すけらて、後者他のモジュールから不正アクセスを[[コンパイル時]]にチェックできるようにった。この機能は{{仮リンク|情報隠蔽|en|information hiding}}と呼ばれる


また、モジュール内の手続きと[[構造体]]の実装部分を隠蔽して、定義部分だけをグローバル公開することもできる。この機能は{{仮リンク|Abstraction (computer science)|en|Abstraction (computer science)|label=抽象化}}と呼ばれる。[[C言語]]で例えると定義部分とは[[ヘッダファイル]]、実装部分とは[[ソースコード]]である。手続きの定義部分とは「返り値型+手続き名+引数リスト」の[[関数プロトタイプ]]を指す。[[構造体]]の定義部分とはタグ名を指す。抽象化された構造体は、同モジュール内の手続きのための引数と返り値用途限定になり、フィールドにはアクセスできないものとなる。このモジュールでの抽象化機能は、定義部分に当てはめる実装部分のモジュールを[[コンパイラ|コンパイル]]時や[[リンケージエディタ|リンカ]]時に選択できるようにしている。
手続き型プログラミングと[[構造化プログラミング]]は混同されやすいが、後者は順接、分岐、反復の三つの[[制御構造|制御構造文]]を多用するコーディングスタイルに焦点を当てたパラダイムである。分岐はIF文、反復はWHILE文やFOR文で表現される。GOTO文を安易に用いないことが推奨されている。手続きによるプログラムコードの階層分割は、構造化と同義でもあるのでパラダイム上の重複面は否定できない。[[エドガー・ダイクストラ]]が考案した元祖構造化プログラミングの方では、上述のモジュールが構造化に向けた階層分割単位になり、そのモジュールは抽象化と統合詳細化に分離されていた。抽象化は、抽象データ構造体と抽象ステートメントのセットと言われ、上述の手続きの定義部分と構造体の定義部分のセットに似たものと見なせる。統合詳細化は、手続きの実装部分と構造体の実装部分のセットに似たものと見なせる。

=== 構造化とは ===
手続き型プログラミングと[[構造化プログラミング]]は、同じテーマでよく用いられる言葉である。構造化プログラミングの定義はやや曖昧であるが、コード記述視点とプログラム設計視点の二つから解釈される。前者のコード記述視点では、順接・分岐・反復の三つの[[制御構造|制御構文]]を用いて[[goto文]]を極力用いないソースコード記述を重視したプログラミングスタイルになる。[[構造化定理]]がよく引き合いに出されて、それに[[サブルーチン]]による適切なプログラム分割が加えられることもある。

後者のプログラム設計視点では、プログラム全体の適切な[[モジュール]]分割を図り、各モジュールの[[凝集度]]およびモジュール間の[[結合度]]の適切な設定を重視したプログラミングパラダイムになる。プログラム全体をモジュールの組み合わせとそれらの連携で構築しようとする考え方である。このモジュールによるプログラムの構造化は、1970年前後から盛んに研究され始めて「structured design(SD)」「structured analysis(SA)」「[[ジャクソンの構造化プログラミング|Jackson structured programming]](JSP)」「[[構造化分析設計技法|structured analysis and design technique]](SADT)」「[[SSADM|structured systems analysis and design method]](SSADM)」「modern structured analysis」といった数々の[[ソフトウェア工学]]テクニックが発表されている。グレンフォード・マイヤーズ、[[エドワード・ヨードン]]、[[トム・デマルコ]]などが有名である。


=== 命令型プログラミング ===
=== 命令型プログラミング ===
手続き型は[[命令型プログラミング]]の分類に属している。これが意味する主なプログラム上の枠組みは、手続きはパラメータ無しでもよくリターン値がなくてもよい、手続き内ではグローバル変数とローカル静的変数とその他のプログラム状態が自由に変更される、手続きは渡されたパラメータ以外の情報要素の影響を無制限に受けるので同じパラメータに対するリターン値は一定でない、の三点になる。
手続き型プログラミングは[[命令型プログラミング]]の分類に属している。これが意味する主なプログラム上の枠組みは、(A)手続きはパラメータ無しでもよくリターン値がなくてもよい、(B)手続き内ではグローバル変数とローカル静的変数と外部環境データが自由に変更される、(C)手続きは渡されたパラメータ以外の外部データおよび外部環境状態の影響を無制限に受けるので同じパラメータに対する処理内容とそのリターン値は一定でない、の三点になる。

また、手続き型プログラミングと命令型プログラミングは、[[オートマトン|オートマタ理論]]の[[非決定性有限オートマトン]]見地からの同じ意味で用いられてもいる。手続き的(''procedual'')の用法としてはこちらの方が標準である。この「手続き的」の意味も上述の(A)(B)(C)と同じである。


== 歴史 ==
== 歴史 ==
手続き(''procedure'')の考え方自体はコンピュータ黎明期の[[機械語]]コードの時代から存在している。手続きの実装方式は、アセンブラなどの[[低水準言語]]で用いられる[[ニーモニック・コード|ニーモニックコード]]のCALL命令とRET命令が原点である。PUSH命令による引数の[[スタック|スタックメモリ]]への積み込みと、スタックポインタレジスタの減算による自動変数領域の確保、ベースポインタレジスタによる引数と自動変数の参照といった[[スタックフレーム]]の機能もアセンブラ由来のものである。CALL命令のジャンプ先アドレスに付けられたラベルは手続き名と同義になった。その仕組みは1950年代半ばから登場した[[高水準言語]]にもそのまま受け継がれた。ラベルは形式化されたパラメータリスト付きのプロシージャネームになり、スタックフレーム処理も自動化され、命令コード行のかたまりはソースコード上で明確に区分けされたコードブロックとして形式化された。こうして手続き(プロシージャ)は低水準言語から高水準言語への移行期に言わばごく自然に誕生している。
手続き(''procedure'')の考え方自体はコンピュータ黎明期の[[機械語]]コードの時代から存在している。手続きの実装方式は、アセンブラなどの[[低水準言語]]で用いられる[[ニーモニック・コード|ニーモニックコード]]のCALL命令とRET命令が原点である。PUSH命令による引数の[[スタック|スタックメモリ]]への積み込みと、スタックポインタレジスタの減算による自動変数領域の確保、ベースポインタレジスタによる引数と自動変数の参照といった[[スタックフレーム]]の機能もアセンブラ由来のものである。CALL命令のジャンプ先アドレスに付けられたラベルは手続き名と同義になった。その仕組みは1950年代半ばから登場した[[高水準言語]]にもそのまま受け継がれた。ラベルは形式化されたパラメータリスト付きのプロシージャネームになり、スタックフレーム処理も自動化され、命令コード行のかたまりはソースコード上で明確に区分けされたコードブロックとして形式化された。こうして手続き(プロシージャ)は低水準言語から高水準言語への移行期に言わばごく自然に誕生している。


なお、史上初の高水準言語として1954年に公開されたFORTRANは、CALL命令とRET命令を備えていなかったので手続きの形式も持っていなかった。初代FORTRANのプログラムはPROGRAMという定義文で括られた一つのメインルーチンで記述されていた。故にこれは非手続き型言語である。プログラム規模の急速な拡大ですぐにサブルーチン構造も必要になり、1958年に発表されたFORTRANⅡではCALL命令とRET命令が追加された。メインルーチンから分けられたサブルーチンは「''External Procedure''(外部手続き、邦訳は外部副プログラム)」と定義され、その呼び出しを多用するプログラム記述がなされるようになった。これが手続き型プログラミングの始まりである
なお、史上初の高水準言語として1954年に公開されたFORTRANは、CALL命令とRET命令を備えていなかったので手続きの形式も持っていなかった。初代FORTRANのプログラムはPROGRAMという定義文で括られた一つのメインルーチンで記述されていた。故にこれは非手続き型言語である。プログラム規模の急速な拡大ですぐにサブルーチン構造も必要になり、1958年に発表されたFORTRANⅡではCALL命令とRET命令が追加された。メインルーチンから分けられたサブルーチンは「External Procedure(外部手続き、邦訳は外部副プログラム)」と定義され、その呼び出しを多用するプログラム記述がなされるようになった。


== 他のパラダイムとの対比 ==
== 他のパラダイムとの対比 ==
=== オブジェクト指向プログラミング ===
=== オブジェクト指向プログラミング ===
[[オブジェクト指向プログラミング|オブジェクト指向]]の原点の一つは[[グローバル変数]]問題の解決であ。オブジェクト指向では変数と手続きを共に[[クラス (コンピュータ)|クラス]]に所属させて管理する。クラスは変数と手続きをひとまとめにしてグループ化したものである。手続き型の[[モジュール化|モジュール]]似ているが、前節で説明した「抽象化」の実装スタイルに大きな違いがある。クラスの方では変数集合に手続きを直接所属させるという形でより明にデータの抽象化を表現している。抽象化されたデータはそれ専用の手続きを通したアクセス限定になるからである。これは[[カプセル化]]という用語で説明されている。また手続きの抽象化では、定義部分から呼び出される実装部分の選択をコンパイル時だけではなく更に実行時にも分岐できるようにしている。これは[[多態性]]という用語で説明されており、[[継承 (プログラミング)|継承]]という構造の上で実装されている。
[[オブジェクト指向プログラミング|オブジェクト指向]]の発端の一つは[[グローバル変数]]問題の解決策としてであった。オブジェクト指向では変数と手続きを共に[[クラス (コンピュータ)|クラス]]に所属させて管理する。クラスは変数と手続きをひとまとめにしてグループ化したものである。手続き型の[[モジュール化|モジュール]]似ているが、前節で説明した「[[抽象化 (計算機科学)|抽象化]]」の実装スタイルに大きな違いがある。クラスの方では、直接アクセスを禁止した変数集合にそのアクセス専用の手続きを付随させるという形より明にデータの抽象化を表現している。これは[[カプセル化]]という用語で説明されている。手続きの抽象化では、定義部分から呼び出される実装部分の選択を[[コンパイラ|コンパイル]]時または[[リンケージエディタ|リンカ]]時だけではなく更に実行時にも分岐できるようにしている。これは[[多態性]]という用語で説明されており、[[継承 (プログラミング)|継承]]という構造の上で実装されている。
{| class="wikitable"
{| class="wikitable"
|-
|-
52行目: 62行目:


=== 関数型プログラミング ===
=== 関数型プログラミング ===
手続き型ではデータへの作用(読込と書込)を[[ステートメント]]と呼ばれる命令コードの一行単位で順々に実行していく。それに対して[[関数型言語|関数型]]では各データを関数演算子でそれぞれつなげた「[[式 (プログラミング)|式]]」として一意に表現し、その式を[[評価]]または簡約するという流れの中で命令コードを実行する。評価は計算と同義であり、計算された式は結果値として新たなデータに変わる。その新たなデータは[[束縛変数|変数]]に束縛されるなどして後続の式で用いられるといった繰り返しになる。関数は1個以上の引数をつの返り値に変換する機能であり、その返り値=値と同一視される。すなわち関数=値であるので関数は他の関数に引数として渡すこともできるし、返り値として渡すこともできる。この仕組みは[[高階関数]]と呼ばれ、関数型の代表的スタイルでもある。
手続き型ではデータへの作用(読込と書込)を[[ステートメント]]と呼ばれる命令コードの一行単位で順々に実行していく。それに対して[[関数型言語|関数型]]では各データを関数/演算子でそれぞれつなげた「[[式 (プログラミング)|式]]」として一意に表現し、その式を[[評価]]または簡約するという流れの中で命令コードを実行する。評価は計算と同義であり、計算された式は結果値として新たなデータに変わる。その新たなデータは[[束縛変数|変数]]に束縛されるなどして後続の式で用いられるといった繰り返しになる。関数は1個以上の引数を、1つの返り値に変換する機能であると同時にその返り値と同一視される存在である。すなわち関数=値であるので関数は他の関数に引数として渡すこともできるし、他の関数の返り値こともできる。この仕組みは[[高階関数]]と呼ばれ、関数型の代表的特徴とされている。
{| class="wikitable"
{| class="wikitable"
|-
|-
72行目: 82行目:


=== 論理型プログラミング ===
=== 論理型プログラミング ===
[[論理型プログラミング|論理型]]は「AはZである」「AとBはZである」「AはBのZである」「AはBをZする」といった定義節(確定節)をサブル集合として記述し、それに対して「AはZであるか?」「Aと?はZであるか」といった質問節(目標節)をメインルーチンにして解を導き出すといった形でプログラムを構築する。定義節はリテラと呼ばれる[[原子論理式]]で構成されるが、予めリテラルとして用意されている組み込みサブル、代入文、比較文を用いることにより、解を導き出すという流れの中で手続き型と同様の命令を実行できる。
[[論理型プログラミング|論理型]]は「AはZである」「AとBはZである」「AはBのZである」「AはBをZする」といった知識を定義する[[論理式 (数学)|論理式]]風の[[ステトメト]]群を記述し、それに対して「AはZであるか?」「Aと?はZであるか」といった質問を表わす[[論理式 (数学)|論理式]]をメインルーチンにして解を導き出すといった形でプログラムを構築する。命令コードをシーケンシャに順接実行す手続き型に対して、論理型では[[パターンマッチング]]による選択別実行が中心になっている。論理式には[[原子論理式]]としての[[入出力]]命令や[[アプリケショプログラミングインタフェース|API]]命令も含めることが出来るので、解を導き出すという流れの中で手続き型と同様の命令コードを実行できる。
{| class="wikitable"
{| class="wikitable"
|-
|-
78行目: 88行目:
! 手続き型
! 手続き型
|-
|-
| 節集合
| 単位
| モジュ
|
|-
|-
| 確定節
| 確定節
| 手続き
| 手続き
|-
|-
|目標節
| リテラル
|メインルーチン
|-
| 原子論理式
| ステートメント
| ステートメント
|-
|-
92行目: 105行目:
|導出節
|導出節
|リターン値
|リターン値
|-
|目標節
|メインルーチン
|}
|}


== 代表的な手続き型言語 ==
== 代表的な手続き型言語 ==
手続き型プログラミングは、1958年公開の[[FORTRAN|FORTRANⅡ]]、[[COBOL]]、[[ALGOL]]といった最も初期の[[高水準言語]]から導入されている。1970年代後半からほとんどの手続き型言語はマルチパラダイム化した。その代表的な発展形は[[オブジェクト指向プログラミング|オブジェクト指向]]である。以下の言語一覧にも後年にオブジェクト指向を導入しているものがあるが、それまでの期間が比較的長かったものを手続き型に入れている。
1970年代後半からほとんどの手続き型言語はマルチパラダイム化しており、その代表的な発展形は[[オブジェクト指向プログラミング|オブジェクト指向]]である。以下の言語一覧にも後年にオブジェクト指向を導入しているものがあるが、それまでの期間が比較的長かったものを手続き型に入れている。


*[[FORTRAN|'''FORTRANⅡ''']] - 1958年、初の手続き型言語。その構文は[[低水準言語]]の特徴を少し残していた。
*[[FORTRAN|'''FORTRANⅡ''']] - 1958年、初の手続き型言語。その構文は[[低水準言語]]の特徴を少し残していた。
108行目: 118行目:
* '''[[Pascal]]''' - 1970年、構造化プログラミングの模範言語。教育向け。
* '''[[Pascal]]''' - 1970年、構造化プログラミングの模範言語。教育向け。
*'''[[C言語]]''' - 1972年、最も広く受け入れられた手続き型言語。
*'''[[C言語]]''' - 1972年、最も広く受け入れられた手続き型言語。
* '''[[Modula-2]]''' - 1978年、[[モジュール]]を導入した初のモジュール化言語。
* '''[[Modula-2]]''' - 1978年、[[モジュール]]を導入したのモジュー言語。
*'''[[Ada]]''' - 1983年、[[米国国防総省|米国防総省]]が開発した[[マルチパラダイムプログラミング言語|マルチパラダイム]]手続き型言語。モジュール、[[ジェネリックプログラミング|ジェネリクス]]、[[並行計算]]など。
*'''[[Ada]]''' - 1983年、[[米国国防総省|米国防総省]]が開発した[[マルチパラダイムプログラミング言語|マルチパラダイム]]手続き型言語。モジュール、[[ジェネリックプログラミング|ジェネリクス]]、[[並行計算]]など。
*'''[[QuickBASIC]]''' - 1985年、構造化されたBASIC。[[MS-DOS]]用。
*'''[[QuickBASIC]]''' - 1985年、構造化されたBASIC。[[MS-DOS]]用。
*'''[[Oberon]]''' - 1987年、マルチパラダイム。[[抽象メソッド|抽象手続き]]をまとめたレコード[[インタフェース (抽象型)|インターフェース]]に類似)が導入された。
*'''[[Oberon]]''' - 1987年、マルチパラダイム。[[抽象メソッド|抽象手続き]]をまとめたレコード(≒[[インタフェース (抽象型)|インターフェース]])が導入された。
*'''[[Perl]]''' - 1987年、[[Webアプリケーション|WEBアプリケーション]]開発向け。
*'''[[Perl]]''' - 1987年、[[Webアプリケーション|WEBアプリケーション]]開発向け。
*[[Microsoft Visual Basic|'''Visual Basic''']] - 1991年、[[Microsoft Windows|Windows]]アプリケーション開発用のビギナー向け言語。
*[[Microsoft Visual Basic|'''Visual Basic''']] - 1991年、[[Microsoft Windows|Windows]]アプリケーション開発用のビギナー向け言語。

2021年1月24日 (日) 04:54時点における版

手続き型プログラミング(てつづきがたプログラミング、: Procedural programming)は、手続き(procedure)の定義と呼び出しをプログラムを組み立てる基礎にしたプログラミングパラダイムである。手続きは言語によってサブルーチン、関数、サブプログラムとも呼ばれており、プログラムの命令コードのまとまりを任意の手続き名で抽象化したコードユニットである。手続きは入力されたパラメータ引数によって処理内容を変化させ、処理結果となるリターン値を出力する事ができる。パラメータ引数、リターン値、その双方を持たない手続きも定義できる。

手続きは一般的に非決定性有限オートマトンに準拠しているので、入力値による処理内容とそこからの出力値は、その手続き枠外の外部環境状態によっても変化する。この遷移図の可変性を指して手続き的(procedual)とも言われる。1958年のFORTRANⅡALGOLCOBOLといった最も初期の高水準言語から導入されている。

特徴

手続き型プログラミングでは基本的に、起点になるメインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きからアクセス可能なグローバル変数集合といったプログラム構成になる。複数の手続きからアクセスされるあらゆるデータを、グローバルにまとめてしまう簡素な設計は、プログラム全体への理解をむしろ促進するものとして小中規模のソフトウェア開発には適したものとされている。

手続きとは

手続きprocedure)は、命令コード(instruction code)のまとまりをパラメータリスト付きの識別子に結び付けたコードユニットである。識別子は同時にリターン値の代入対象になる。命令コードの一行単位はステートメント(statement)と呼ばれる。プログラム起点のメインルーチンは手続きとは見なされない。手続きはプログラム内のあらゆるポイントから呼び出すことができる。手続き内の終端位置に達した時は、その呼び出しポイントの次の命令コードにリターンされる。手続き内の途中位置からでもリターンできる。手続きの呼び出しとリターンは、一般にコンピュータ側が提供するコールスタック機能によって実現されている。

手続きの名称は言語によって異なっている。COBOLでは副プログラムが手続き相当であり、副プログラムの命令コード記述部分を手続き部と呼んだ。FORTRANではリターン値を持たない方の手続きをサブルーチン、持つ方の手続きを関数と呼んだ。ALGOLではどちらも手続きと呼び、C言語ではどちらも関数と呼んだ。Pascalではリターン値を持たない方を手続き、持つ方を関数とした。BASICではリターン値無しをサブプログラム、有りを関数と呼んだ。なお、FORTRANのサブルーチン&関数はその内部サブルーチン&内部関数を複数定義可能でそれらをまとめたものを外部手続き(邦訳は外部副プログラム)と定義している。

手続きの識別子は手続き名と呼ばれる。パラメータリストには任意の個数の引数が列挙される。引数無しのケースもある。手続き名+パラメータリストの次にコードブロックが置かれる。コードブロックには1行以上の命令コードが列記される。命令コードは引数の使用によってプロセスの多相性を実現できる。コードブロックはレキシカルスコープの範囲になり、そのスコープ専用のローカル変数を定義できる。ローカル変数は静的変数と自動変数に分かれる。静的変数はプログラム実行中を通して代入値が保持される。自動変数はスタックフレームを利用したもので、手続き内へのエントリと共に自動確保され、リターンと共に自動消去されるものである。命令コード行の終端に達するとリターンする。リターンとはその手続きの呼び出しポイントの次の命令行に移動することである。プロセスの結果を示すリターン値は手続きの識別子を媒体にして呼び出し元に渡される。リターン命令で途中位置からのリターンもできる。スタックフレームの利用により手続きは再帰呼び出しも可能である。

手続き型プログラムの構成

手続き型プログラムは、メインルーチンをルートにして階層的に分割された無数の手続きと、全ての手続きからアクセス可能なグローバル変数集合によって構成される。プログラムのコードは手続き単位に分割される。プログラムのデータはグローバル変数とローカル変数に大別される。ローカル変数は各手続きのスコープ内に分散配置されてその手続き専用になる。グローバル変数スコープには、複数の手続きからアクセスされる様々な変数が雑然と置かれることになる。したがって特定のグローバル変数を特定の手続きグループ専用にするといった設計はプログラマの注意力に委ねられた。どの手続きから参照されており、またどの手続きから変更されているかの把握が難しいグローバル変数は、バグの温床になることが多かった。これはグローバル変数問題などと呼ばれて1960年代前半には指摘されるようになっていた。

モジュール機能

グローバル変数問題の解決のために、任意の「手続きグループ+変数グループ」をまとめてモジュールとして定義できる機能が誕生した。この機能を中心にしたパラダイムはモジューラプログラミング英語版と呼ばれた。これは手続き型プログラミングの最も身近な発展形である。グローバルスコープとローカルスコープの間にモジュールスコープが追加され、モジュール内アクセス限定の手続きと変数を定義できるようになった。モジュール内の手続きと変数は、グローバル公開するものとモジュール内限定するものに分けられて、後者は他のモジュールからの不正アクセスをコンパイル時にチェックできるようになった。この機能は情報隠蔽と呼ばれる。

また、モジュール内の手続きと構造体の実装部分を隠蔽して、定義部分だけをグローバル公開することもできる。この機能は抽象化英語版と呼ばれる。C言語で例えると定義部分とはヘッダファイル、実装部分とはソースコードである。手続きの定義部分とは「返り値型+手続き名+引数リスト」の関数プロトタイプを指す。構造体の定義部分とはタグ名を指す。抽象化された構造体は、同モジュール内の手続きのための引数と返り値用途限定になり、フィールドにはアクセスできないものとなる。このモジュールでの抽象化機能は、定義部分に当てはめる実装部分のモジュールをコンパイル時やリンカ時に選択できるようにしている。

構造化とは

手続き型プログラミングと構造化プログラミングは、同じテーマでよく用いられる言葉である。構造化プログラミングの定義はやや曖昧であるが、コード記述視点とプログラム設計視点の二つから解釈される。前者のコード記述視点では、順接・分岐・反復の三つの制御構文を用いてgoto文を極力用いないソースコード記述を重視したプログラミングスタイルになる。構造化定理がよく引き合いに出されて、それにサブルーチンによる適切なプログラム分割が加えられることもある。

後者のプログラム設計視点では、プログラム全体の適切なモジュール分割を図り、各モジュールの凝集度およびモジュール間の結合度の適切な設定を重視したプログラミングパラダイムになる。プログラム全体をモジュールの組み合わせとそれらの連携で構築しようとする考え方である。このモジュールによるプログラムの構造化は、1970年前後から盛んに研究され始めて「structured design(SD)」「structured analysis(SA)」「Jackson structured programming(JSP)」「structured analysis and design technique(SADT)」「structured systems analysis and design method(SSADM)」「modern structured analysis」といった数々のソフトウェア工学テクニックが発表されている。グレンフォード・マイヤーズ、エドワード・ヨードントム・デマルコなどが有名である。

命令型プログラミング

手続き型プログラミングは命令型プログラミングの分類に属している。これが意味する主なプログラム上の枠組みは、(A)手続きはパラメータ無しでもよくリターン値がなくてもよい、(B)手続き内ではグローバル変数とローカル静的変数と外部環境データが自由に変更される、(C)手続きは渡されたパラメータ以外の外部データおよび外部環境状態の影響を無制限に受けるので同じパラメータに対する処理内容とそのリターン値は一定ではない、の三点になる。

また、手続き型プログラミングと命令型プログラミングは、オートマタ理論非決定性有限オートマトン見地からの同じ意味で用いられてもいる。手続き的(procedual)の用法としてはこちらの方が標準である。この「手続き的」の意味も上述の(A)(B)(C)と同じである。

歴史

手続き(procedure)の考え方自体はコンピュータ黎明期の機械語コードの時代から存在している。手続きの実装方式は、アセンブラなどの低水準言語で用いられるニーモニックコードのCALL命令とRET命令が原点である。PUSH命令による引数のスタックメモリへの積み込みと、スタックポインタレジスタの減算による自動変数領域の確保、ベースポインタレジスタによる引数と自動変数の参照といったスタックフレームの機能もアセンブラ由来のものである。CALL命令のジャンプ先アドレスに付けられたラベルは手続き名と同義になった。その仕組みは1950年代半ばから登場した高水準言語にもそのまま受け継がれた。ラベルは形式化されたパラメータリスト付きのプロシージャネームになり、スタックフレーム処理も自動化され、命令コード行のかたまりはソースコード上で明確に区分けされたコードブロックとして形式化された。こうして手続き(プロシージャ)は低水準言語から高水準言語への移行期に言わばごく自然に誕生している。

なお、史上初の高水準言語として1954年に公開されたFORTRANは、CALL命令とRET命令を備えていなかったので手続きの形式も持っていなかった。初代FORTRANのプログラムはPROGRAMという定義文で括られた一つのメインルーチンで記述されていた。故にこれは非手続き型言語である。プログラム規模の急速な拡大ですぐにサブルーチン構造も必要になり、1958年に発表されたFORTRANⅡではCALL命令とRET命令が追加された。メインルーチンから分けられたサブルーチンは「External Procedure(外部手続き、邦訳は外部副プログラム)」と定義され、その呼び出しを多用するプログラム記述がなされるようになった。

他のパラダイムとの対比

オブジェクト指向プログラミング

オブジェクト指向の発端の一つはグローバル変数問題の解決策としてであった。オブジェクト指向では変数と手続きを共にクラスに所属させて管理する。クラスとは変数と手続きをひとまとめにしてグループ化したものである。手続き型のモジュールに似ているが、前節で説明した「抽象化」の実装スタイルに大きな違いがある。クラスの方では、直接アクセスを禁止した変数集合にそのアクセス専用の手続きを付随させるという形式で、より明確にデータの抽象化を表現している。これはカプセル化という用語で説明されている。手続きの抽象化では、定義部分から呼び出される実装部分の選択をコンパイル時またはリンカ時だけではなく更に実行時にも分岐できるようにしている。これは多態性という用語で説明されており、継承という構造の上で実装されている。

オブジェクト指向 手続き型
クラス モジュール
インスタンス 構造体
メソッド 手続き
データメンバ 変数

関数型プログラミング

手続き型ではデータへの作用(読込と書込)をステートメントと呼ばれる命令コードの一行単位で順々に実行していく。それに対して関数型では各データを関数/演算子でそれぞれつなげた「」として一意に表現し、その式を評価または簡約するという流れの中で命令コードを実行する。評価は計算と同義であり、計算された式は結果値として新たなデータに変わる。その新たなデータは変数に束縛されるなどして後続の式で用いられるといった繰り返しになる。関数は1個以上の引数を、1つの返り値に変換する機能であると同時にその返り値と同一視される存在である。すなわち関数=値であるので関数は他の関数に引数として渡すこともできるし、他の関数の返り値にすることもできる。この仕組みは高階関数と呼ばれ、関数型の代表的特徴とされている。

関数型 手続き型
ステートメント
関数 手続き
代数的データ型 基本型構造体
束縛変数と自由変数 変数

論理型プログラミング

論理型は「AはZである」「AとBはZである」「AはBのZである」「AはBをZする」といった知識を定義する論理式風のステートメント群を記述し、それに対して「AはZであるか?」「Aと?はZであるか」といった質問を表わす論理式をメインルーチンにして解を導き出すといった形式でプログラムを構築する。命令コードをシーケンシャルに順接実行する手続き型に対して、論理型ではパターンマッチングによる選択別実行が中心になっている。論理式には原子論理式としての入出力命令やAPI命令も含めることが出来るので、解を導き出すという流れの中で手続き型と同様の命令コードを実行できる。

論理型 手続き型
単位節 データ
確定節 手続き
目標節 メインルーチン
原子論理式 ステートメント
単一化 手続き呼び出し
導出節 リターン値

代表的な手続き型言語

1970年代後半からほとんどの手続き型言語はマルチパラダイム化しており、その代表的な発展形はオブジェクト指向である。以下の言語一覧にも後年にオブジェクト指向を導入しているものがあるが、それまでの期間が比較的長かったものを手続き型に入れている。

関連項目

外部リンク