演算、手続き、ルーチン、モジュール、関数、メソッド

先日、関数とメソッドの違いを学生に聞いたら、分からんとのこと。せっかくなので自分的に整理する。

コンピュータの一番最初の処理――演算から。

演算

コンピュータは当初、計算のための道具であった。実際、最近まで「計算機」と呼ばれていたのはその名残。

計算のための道具なのだから、それが行う処理は「演算」と呼ばれた。

歴史的な最初の演算は ENIAC が行った弾道計算だろう。

もちろん、いまの最近コンピュータのCPUにも ALU(演算装置)がある。

演算とは、コンピュータが行うごく簡単な計算のことである。

手続き(プロシージャ)

最初は計算(演算)のためのコンピュータだったが、処理の内容を柔軟にし、計算以外にも広げた方が有用であることが分かった。

つまり、処理を演算列から「命令列」に汎化し、「制御命令」を導入して、演算の実行順序を動的に変えた方がいろいろできるのである。もっと単純に言えば、条件付き分岐命令を設けたことで、次に実行する命令をコンピュータの状態によって変えられるようになった。

これには機械的な発展も関係しており、プログラム内蔵方式(命令列をメモリに載せる方式)の採用によって命令位置がアドレスで示せるようになったことが影響している。ちなみに、命令とデータを同一のアドレス空間に格納するものをノイマン型アーキテクチャ、異なる空間に格納するものをハーバードアーキテクチャという。

さて、こうして制御命令が導入されると、当初の演算自体の他に、「どういう演算を行っていくか」という判断もコンピュータが行うようになった。したがって、この判断を含む命令列は単なる演算ではないため、新しく「手続き」と呼ばれるようになった。

手続きとは、演算に制御を加えた処理である。

手続きは、いまでもコンピュータの基本的な動作である。

手続きの実行によって、コンピュータは単なる計算機から進化していった。

ルーチン

さて、演算と制御を手に入れたコンピュータは、問題によっては人間よりも圧倒的に速く解を出せるようになり、徐々に応用範囲が広がっていった。そうしてたくさんの人がプログラムを書くようになると、自然と「みんなが共通して記述する命令列」があることが分かってきた。また(たぶんこちらの方が先だが)ひとりでコーディングしていても、「さっきと同じ命令列」がひとつのプログラム中に散見されるようになってきた。

ルーチンとは、そうした共通して、もしくは繰り返して使用される命令列を取り出して、単独の手続きとして独立させたものである。

現代のプログラムにおいては、大抵の手続きはいつでもルーチンとして再利用できるようになっている。したがって、ルーチンの特性を強調する必要性が薄れ、ルーチンという用語は消えつつあると感じる。

サブルーチン

ルーチンは、繰り返し利用される命令列を取り出したものだが、ルーチンに名前がつけられるようになると、処理の抽象化の手段としても使われるようになった。コールスタックを用いる処理※には入れ子関係があるから、命令列をまとめたルーチン(小さいルーチン) → 小さいルーチンをまとめた大きなルーチン、とルーチン間に全体-部分関係が定義できる。

そこで、より処理範囲の広いルーチンをメインルーチンと呼び、メインルーチンの一部を処理するルーチンをサブルーチンと呼ぶようになった。

※ちなみにCPSスタイルの処理は入れ子関係がない……と思う。

高水準言語の効果

アセンブリ言語のプログラムでは、再入可能性再帰可能性再配置可能性などを確保することが難しかった。これらはコンパイラ・リンカなどの技術を集めて高水準言語C言語など)で解決され、現在のプログラミングモデルを支えている。

モジュール

メインルーチン−サブルーチンによってプログラムが階層構造を取れるようになったが、一方で対等なルーチン同士をグループ化する方法が必要とされた。

そこで新たに「モジュール」という概念を導入し、呼び出し関係はないが、意味的につながりの強いルーチン同士をまとめられるようにした。モジュールの役割は意味的グルーピングだが、プログラミング言語処理系によっては可視性の制御など付加的な機能を与えるものもある。

C言語においては、コンパイル単位 (.c ファイル) とモジュールが同一視されることもある。これは static グローバル変数の可視範囲がコンパイル単位になるためだと勝手に推測する。

関数

さて、コンピュータがある程度速くなってくると、極限的最適化を多少犠牲にしても、当事者の抱える問題をなるべく直感的にプログラムしたいという要求が生まれてきた。

どういうことかというと、それまではたとえば簡単な計算であっても、メモリからデータをロードして演算命令で計算してデータをストアして……と、およそ「計算」とは異なる知識が必要だった。しかし、そもそも命令列とアドレスという概念は非常にコンピュータ特有で、コンピュータの専門家以外は分からんのである。

そこで、コンピュータの専門知識が(あまり)なくても利用しやすくするために、プログラムを数学の知識を援用して記述できるようにした。この代表格がプログラミング言語での「関数」のサポートであり、これによって「なんとなく数学っぽく」書けるようになったのである。

したがって、「関数」という言葉は2種類の意味がある。ひとつめは数学の関数である。説明不要であろう。ふたつめは、数学の素養のある技術者のプログラム作成を助けるために、各プログラミング言語が独自で用意した「数学の関数に似た何か」である。

関数を取り入れたもっとも普及している言語はC言語だろう。C言語では手続きはすべて関数として表現される。また、sin(x) などとして呼び出し処理を行わせることができる。たしかに数学の関数に似ている。

しかし、C言語と数学の関数は本質的に異なり、それは副作用として現れている。C言語の関数は「副作用」の有無が重要だが、数学の関数にはそもそも副作用という概念は存在しない。ふたつの関数は、記法と振る舞いを似せてはいるものの、異なる存在である。

数学の関数をより忠実にコンピュータで実現したプログラミング言語は、一般に純粋関数型プログラミング言語と呼ばれる。それらの振る舞いは数学に近いが、しかしたとえばコンピュータは限定的にしか無限を扱えないなど、同じものではない。

メソッド

C言語の関数が数学に由来したように、他の思想から由来した手続きも存在する。その代表例がメソッドである。メソッドを備える言語には C#, Java, C++ がある。

メソッドの由来はクラス指向プログラミングである。クラス指向プログラミングはオブジェクト指向プログラミングの拡張であり、オブジェクト間のメッセージパッシングをクラス間のメソッドの呼び出しで実現する。

端的に言えば、メソッドも手続きの一種である。特徴的なところは、そのメソッドを処理するべきインスタンスのアドレスを必ず受け取ることである。

手続き、関数、メソッドの違い

よく混同されることから、手続きと関数、メソッドの違いを明確にする。

関数は手続きの一種である。メソッドも手続きの一種である。手続きとは命令列のことである。

手続きに対する関数の特徴は、副作用がなく、結果がパラメタのみで決まることである。この性質を実現するために、C言語ではスタックや呼び出し規約の策定などを行っている。もしスタックがなければ、C言語で再入可能・再帰可能な関数を記述することは困難になる。もし呼び出し規約がなければ、いかなる関数呼び出しも副作用が発生する(そもそもどんな呼び出しもCPUレジスタを破壊する。根本的に副作用のない関数などあり得ない)。なお、C言語では数学的関数の定義から逸脱した関数を記述することができるが、それはすでにC言語の「手続き」の別名でしかない。この、同じ単語を違う概念(悪いことに見た目は似せた)に当ててしまったことによる混同は、いまもずっと続く不幸である。

手続きに対するメソッドの特徴は、パラメタとして処理対象のオブジェクトを必ず一つは受け取ることである。より詳しくプロパティと対比した場合は、メソッドは副作用を持つことが特徴となる。

関数とメソッドの違いは、それらが由来する思想が違うのである。もちろん、最終的にはコンピュータで実行できるようどちらも手続きの一種に変換されるが、副作用の有無やパラメタなどに元々の思想による違いがみられる。

関数もメソッドも、もともとはコンピュータの機械的仕組みと直接関係しない概念を扱ったものである。このような、人の認識する問題構造をより直接的にコンピュータに扱わせる方向の進化(Prolog, エージェント指向アスペクト指向、Executable UML, SysML, Symlimk, MDD, ドメイン駆動開発, 形式手法、RDBMS, OODBMS などなど)は、今日も続くソフトウェアの技術革新である。