読者です 読者をやめる 読者になる 読者になる

プログラミングにおける〈式〉についての考察

プログラミング 哲学

「プログラムとは何か」という質問に対する、最も素朴な回答は「プログラムとは、機械に与える命令文の並びのことです」というものだ。命令文を解釈する機械が、プログラムに記された命令に従って、装置を制御し、計算処理を実行する。このようなことは、情報科学の基礎として学校で教えていると思う。

一方で、今日の高水準言語によるプログラミングでは、〈式〉という概念を理解していることがとても重要だ。特に、関数型プログラミングが注目されるようになった今日では、もはや単なる数式としての理解では十分でない。

しかし、プログラミング言語における〈式〉は〈サブルーチン〉や〈関数〉のような概念に比べると自明であるように思われているためか、あまり精緻な説明がなされないようだ。実際、Wikipediaにおける式 (プログラミング) - Wikipediaの記述の短さを見て欲しい。英語版であれば少しだけ記述が長いが、それでも十分な記述には程遠いものだろう。

プログラミングにおける〈式〉は、もっと語られてもいいはずだ。以下では、プログラミングにおける〈式〉の概念について、いくらか考察してみる。

プログラミング・パラダイムにおける式の観点の違い(概論)

以下、各プログラミング・パラダイムにおいて、式がどのような働きをしているかを考察する。各用語は、簡単な表現で説明できるように再定義しているため、巷での用法とは必ずしも一致しないことに注意を要する。「論理型プログラミング」や「関数型プログラミング」といった用語が、巷ではどのような意味で使われているかというのも興味深い話ではあるが、ここではその議論をしない。

命令型プログラミングにおける式

命令型プログラミングとは、〈プログラムとは命令文の並び〉という観点から行われるプログラミングのことだ。命令文を解釈する機械に命令を与えると、そのとおりに順次処理していくから、計算の仔細な手順(狭義のアルゴリズム)を、命令列によって与えれば、機械に計算を行わせることができる。

しかし、人間は大抵の場合、数学的な概念や計算を数式の形で記述ている。そのような数式を人間の手でアルゴリズムに変換するのは煩わしい。数式を与えると、その式の値を計算してくれる〈評価プログラム〉や、数式を与えると、その式の値を計算するための命令列を出力してくれる〈翻訳プログラム〉があると、楽で良い。〈プログラムとは命令文の並び〉という観点のプログラミング言語でも、高水準言語は当然のように〈数式〉を持っている。FORTRANの名の由来も「数式翻訳」“formula translation” だ。(FORTRAN - Wikipedia

命令型プログラミングにおける式は、ポピュラーな数学におけるような、計算して値を求めるための数式だろう。

論理型プログラミングにおける式

命令型プログラミングが〈プログラムとは命令文の並び〉という観点でプログラムするが、論理型プログラミングは〈プログラムとは平叙文の集まり〉という観点でプログラムする。平叙文というのは、「今年の平均気温は昨年より1度高かった」だとか「晴れている日に軒先に何か洗濯物を吊るしておくと、その洗濯物は乾く」のような、世界に関する事実や法則を表現する文だ。これを論理式の形で表現したものを、表明 (assertion) という。

命令型プログラミングによるプログラムの実行方法は、ただ「実行せよ」という命令であるのに対し、論理型プログラミングによるプログラムは、疑問文の形をとる。たとえば「月曜日の掃除当番は誰ですか」というような疑問を、自由変項 (free variable) という、「誰」や「何」に相当する分からない部分を指す項を含んだ論理式の形で与える。この論理式を、ゴール (goal) という。そうすると、マシンはプログラムされている事実や法則と、数理論理学に基づいた汎用の推論アルゴリズムを使って、ゴールが成り立つような例を見つけ出し、「月曜日の掃除当番は お父さん です」のように疑問に回答するわけだ。

事実と法則を与えておくだけで疑問に答えてくれる、ということが実現できるなら、それは人工知能と呼べるのだけれども、論理型プログラミングで推論がうまくいくのは、ごく限られている場合だけだ。きちんと問題が解けるようにするには、推論アルゴリズムの性質を理解し、適切な形で知識と法則を与えなければならない。だから、論理型プログラミングは、ごく基本的な使い方をする分には難しくないが、高度な使い方をしようとすると、途端に難しくなる。

論理型プログラミングにおける式は、自由変項を含んだ論理式であり、これを充足する解を求めるためのものだ。

関数型プログラミングにおける式

命令型プログラミング、論理型プログラミングを「プログラムとは○○」の形で簡潔に表現してきた。その流れで、関数型プログラミングは〈プログラムとは関数の集まり〉という観点でプログラムすることだと定義する。(繰り返しになるが、世間一般の用法とずれることを厭わず、こう定義している。)

関数は式を抽象したものだ。ここで言う抽象とは、式の可変部分を仮引数 (parameter) によって表すような操作を指す。具体例を挙げると、 10 \times 4 + 2という式があるときに、これを抽象し、f(x, y) = 10 \times x + yという関数を抜き出すことができる。

この関数を使えば、元の式は、関数に実引数 (argument) を指定した f(4, 2)という式で表現できるし、また f(7, 5)という式で 10 \times 7 + 5という式を表現できる。

関数型プログラミングによるプログラムの実行は、関数を含む式の値を計算することだ。ここまでの説明だけでは、関数で複雑なプログラムを書くことが出来るということが想像しがたい。しかし、実践的な関数型プログラミングにおいては、式の値としてポピュラーな数だけではなく、より複雑な構造を持った数学的オブジェクトを扱うので、とても実用的で汎用的なプログラミング・スタイルとなっている。

より一般的な〈式〉概念の説明

ここでは、式とは何かをより包括的に考えられるように、一般的に通用する定義を与え、プログラミング言語における〈式〉概念について考察する。

式 (expression) とは、プログラムのうち、値 (value) を持つ部分を指す。

式の望ましい性質には、次のものがある。(各項目の見出しは、例によってここでの議論における命名であって、一般的な用語とは必ずしも一致しない。)

  • 確定性 (definiteness): 式の値は一意に存在している。
  • 構成性 (compositionality): 式の値は式の構造と部分式の値のみから決定される。
  • 純粋性 (purity): 式は式の値以外の意味を持たない。

確定性があることによって、式の意味が明確に定まる。純粋性と構成性があることによって、部分式を、同じ値を持つ別の式と交換しても、式全体の値や意味が変化しないと言える。このような性質は、式に接している時に暗に了解している性質だ。

しかし、これらの性質を満たしていない式も多く存在している。例えば、 \frac{0}{0}という式の値は不定で、確定性がない。System.currentTimeMillis()という式は評価される時刻によって値が決まるから、構成性がない。System.out.println("Hello")という式は、値以外に〈標準出力へ文字列“Hello”を出力する〉という意味を持ってしまっていて、純粋性がない。

あるプログラミング言語の式が確定性・構成性・純粋性を持たないとしても、式の値の考え方を変えると、これらの性質を持つようになる場合がある。たとえば、「 \frac{0}{0}という式の値は不定」とせず、「 \frac{0}{0}は〈不定値〉という値を持つ」と考えることで、言語の本質的な仕様を変えずとも、式に確定性を持たせることができる。場合によっては、言語全体で式の値についての考え方を大きく変えなければならない場合もあるが、この方法は大抵実践可能だ。更に言えば、文のように元来値を持たないプログラムの構成要素に対しても、値を与えることで式にできる。

文のような、本来値を持っていない構成要素を式にできる、と言われても、抵抗があるだろうと思う。なぜ抵抗があるかというと、文の値を考え、文を式とした場合、言語の本質的な仕様を変えないという前提の下では、本来文だった式とそうでない式との間で、不公平な仕様が残る場合があるからだ。具体的には、もとの言語の仕様が「式の値は変数に代入できるが、文には値がないので代入できない」という仕様であったとすると、文に値を与えたところで、本質的な仕様の変更なしには、文は相変わらず変数に代入可能ではないままだからだ。しかし、「式の値は変数に代入可能であるべきだ」のような、個々の言語において望ましいとされている性質は、ここでは議論せず、まずは上記の3つの性質に絞って議論したいと思う。

つづく。