| A |
Abstract interpretation
抽象解釈と訳されているようである。抽象実行とも訳されることがある。
定義としては、プログラムを全部計算することなく、そのプログラムの意味又は特性を画定することを目的とした部分実行ということになると思う。部分実行であるので、プログラムを全部実行してしまうわけではない。
これは、通常のプログラムの実行と比べると分かりやすい。通常のプログラム実行(通常のインタプリタ等の実行のこと)は、標準解釈(standard interpretation)と呼ばれるが、これとの対比で考えると、抽象解釈とは、コンパイル時に行われ、停止性を持つ、性質の調査には近似的であり、部分的なプログラムの実行であるということができる。それに対して、標準解釈は、実行時に行われ、必ずしも停止するわけではなく、しかし、特性の画定は決定的であり、全部を行うプログラムの実行ということになる。
この抽象解釈は、コンパイル時に行うことができるし、プログラムを全部実行しなくてもよいので、先述のように、プログラムのある特定の性質を分析する為に使用される。例えば、広域解析、モデル検査、プログラムの計算量、計算オブジェクトの共有性などであるが、関数型言語として最も有名な使用例は、正格性分析の際の正格性情報の抽出作業である。正格性分析(strictness analysis)については、その項を参照。ただ、抽象解釈は、プログラムの性質を一般的に決定できる訳ではないし、抜き出そうとする性質如何によってその効果も大きく変わってくるので、プログラムの特性の調査に必ず有効というわけではないことに注意。
具体的技術的な説明は、領域理論(domain theory)の知識が必要なので、とりあえず省略する。
Abstraction
抽象と訳されている。
一般的には、具体的な事物などから、無関係な部分を切り捨て共通の要素を抜き出してまとめることをいい、細部に囚われることなく、問題の基本的な部分に注意を集中できるという効用がある。プログラミング言語においても大体同じ意味である。
プログラミング言語においては、多くの文脈で多くの意味を持って語られるが、基本的には2つのレベルで語られることが多い。つまり、インターフェース(何をするか)及びその実装(どのようにするか)を区別する場合と、もっと低いレベルでは、関数/手続及びデータのパッケージ化を語る場合である。
前者の場合、(命令型言語でも説明されるが)オブジェクト指向言語でよく話される内容なので、ここでは説明しない。関数型言語でも、同じような話をするに過ぎないからである。
後者については、操作の抽象とデータの抽象がある。後者はADTの項を参照、或は、クラス概念の話なので説明しない。
操作の抽象には、2つある。つまり、関数(function)と手続(procedure)である(厳密には、操作の抽象はこの2つに限られる訳ではないと思うが、とりあえず説明の必要はないと思うので省略する)。関数については、数学上の定義があるが、それは説明しない(というか、説明するまでもないと思う)。ここでは、関数型言語と命令型言語の比較に役立つ範囲で説明する。
両者は図式的には以下のように表現することができると思う。

→Command、 Expression、 Referential Transparency、 Purity、 Functional paradigm、 Side effect、 Lambda、 Anonymous function、 Bound variable
[←先頭へ]Alternative
consequent(帰結部)とも言われる。特に適訳はないと思われるが、代替部という訳があり、個人的にも代替部と訳した。
定義は到って簡単で、条件節(C/C++では、if、case等のこと)の各節のことである。例をあげる。
int a;
printf("入力してください: ");
scanf("%d", &a);
if(a>=5) printf("正解\n"); /* (1) */
else printf("不正解\n"); /* (2) */
(1)(2)のprintfが代替部に相当する((1)を特に帰結部といったりする)。条件節においては、最終的に実行される代替部は1つしかない。このような条件節の存在意義・利便性については、特に説明の必要はないかと思われるので省略する。
[←先頭へ]
Anonymous function
unnamed functionとも呼ばれる。無名関数や匿名関数等と訳されている。
定義は、文字通り名前を持たない関数のことである。
命令型言語で通常使用する関数というのは、ほぼ名前がついている。その為、コードを記述する際には、その名前だけを記述すれば良く、個別に関数抽象を書く必要は無い。簡単な例をあげる。
int func(int a) { /* (a) */
return a * a;
}
...
func(5); /* (1) */
...
func(10); /* (2) */
...
func(2); /* (3) */
この場合、(1)、(2)と(3)の関数適用の場所で、個別に(a)のようなfuncの定義を記述していたのでは、非常に面倒であるし、読み難くなるし、更に、保守しづらいコードになってしまう。従って、このような関数funcには通常、(a)のような名前を付ける作業が行われる。このことに関する詳しいことは、binding、declaration、nameの項を参照。→Abstraction、 Binding、 Declaration、 First-class object、 Name、 Lambda、 Application
[←先頭へ]Application
単純にアプリケーションと訳されたりするが、ここで説明される意味では、適用と訳されるのが一般のようである。
正式的にはfunction applicationであり、関数適用と訳される。
意味は、引数に適用された関数のことであり、いたって単純である。つまり、関数はまず定義されるが、その定義された関数を特定の引数を適用する場合のその関数である。具体例を示す。
int func(int a){
return a+a;
}
...
func(5); /* (a) */
...
func(10); /* (b) */
この場合の(a)や(b)のように、5や10等の特定の引数に適用されている関数のことを関数適用という。→Partial application、 Definitional mechanism、 Anonymous function
[←先頭へ]Arity
日本語としては、項数とか訳されているが、単にアリティとも訳されることがある。
定義は、論理学における項の数という、訳したままの意味なのだが、関数プログラミング言語に置換えてその意味を画定するならば、引数の数のことになる。例を挙げる。
int fgetc(FILE *stream); /* アリティ1の関数 */
int fputc(int c, FILE *stream); /* アリティ2の関数 */
int fseek(FILE *stream, long offset, int origin); /* アリティ3の関数 */
[←先頭へ]
Assignment
代入と訳されている。
命令型言語に一般的なコマンドとされている。あるメモリの場所の既存の値を破壊して、他の値に変更することをいう。
一種の(純粋ではない)関数といってもよいかもしれない。例えば、以下のような例を考える。
int x;
if(x=1)
{
cout << "xに1を代入して、1という値が出力される為、この文が表示される。\n";
}
代入は、純粋な関数型言語では排除されている。というのも、代入演算子は副作用(side effect)を伴う為、参照透明性(referential transparency)が破壊されてしまうからである。参照透明性と副作用については、各項を参照。→Command、 SICP、 Substitution、 Destructive update(assignment)、 Variable
[←先頭へ]| B |
B&W
勝手につけた名前ですが、「関数プログラミング」(by Bird & Wadler、近代科学社)のことです。
古い本ですが、関数プログラミングに関わる本でめぼしいものといえば、これとSICPぐらいしか見当たらないので、これも参照して下さい。
関数型言語の本というより、関数プログラミングの入門書で、関数プログラムをどのように構成すればよいかを論じています。Mirandaライクな言語で説明しているので、それに似た文法を持つCleanでも十分使えます。
→SICP
[←先頭へ]Binding
束縛と訳されている。単にバインド、バインディングという場合もある。
これは別に関数型言語でなくても基本的なことなのだが、簡単に言うと、名前・識別子をオブジェクト(定数、変数、関数等)の実体に結びつけること又はその状態を言う。
C/C++等では、宣言・定義がこれを行う。
このことの意義は、プログラムの可読性を高める為だが、他にも、プログラムの柔軟性を高める為であるとも言える。というのも、実体が変更されなければならない場合には、その実体の使用されている場所を全て変更するのではなく、単に必要な場所で(「宣言を通じて」と、理解しておいてよいと思う)束縛を変更すればよいということになるからである。
この束縛には、2つの種類があるとされている。つまり、静的束縛(static binding)と動的束縛(dynamic binding)である。詳しくは、各々の項を参照。
また、変数の種類として束縛変数(bound variable)と自由変数(free variable)があり、出現の種類として、束縛出現(binding occurence/bound occurence)と自由出現(free occurence)がある。それぞれの項を参照。
→Occurrence、 Bound variable、 Free variable 、 Environment、 Dynamic binding、 Static binding、 Declaration、 Anonymous function
[←先頭へ]Block
単にブロックといわれている。命令型言語では、複文(compound statement)もこの意味で使用される。
関数型言語ということで無くとも基本的な事柄なので、説明不要であるとも思ったが、一応説明しておくと、定義としては、宣言(decralation)又は定義のスコープ(scope)を限界付けるプログラムのフレーズということになると思う。C/C++, Java,C#等は、"{}"によってこれを行い、Pascalは、"begin-end"によってこれを行う。具体例は挙げなくとも分かると思うので、省略する。宣言とスコープについては、各項を参照。
なお、通常、ブロックは1つではないので、それらの関係を表す必要が出てくる。ブロック間のテキスト上の関係をブロック構造(block structure)という。これには一般に、三つの種類があるとされている。つまり、簡単なものから、モノリシックブロック構造(monolithic)、フラットブロック構造(flat)及び、入れ子ブロック構造(nested)である。大部分の言語は3番目のブロック構造を採用している。ただ、C言語に関しては、手続内部に手続を定義できないので、厳密にはnested block structureを持っているとはいえない部分もなくはないが、概ね三番目のブロック構造を持っていることと同視してよい。
そして、第1のものは、ブロックが一つのプログラムに1つしかない構造をいう。昔のCOBOLはこのようなものであったらしい。2つ目は、一つの入れ子しか認められていないブロック構造である。三番目は、多重入れ子の認められているブロック構造である。入れ子の認められているブロック構造を持つ言語では通常、ブロック内で作られた束縛は、外側の環境(environment)を上書き(隠蔽(shadow))する。特に具体例を示す必要は無いかと思うので、例は省略する。環境については、その項を参照。その他、static binding等も参照。
→Scope、 Environment、 Lifetime、 Bound variable
[←先頭へ]Bound variable
束縛変数と訳されている。
論理学における概念と基本的には同じである。関数型言語の文脈では、ラムダ計算で使用される概念である。これを最初に明確化したのは、ペアノという数学者である。Cleanの翻訳にもペアノ曲線が例として出てくるが、その数学者である。
定義を端的に言うと、あるブロックについて束縛する出現を持つ変数ということになるが、噛み砕くと、束縛されている変数である。もっと簡単に言うと、局所変数(local variable)である。或は仮引数と言うべきかもしれない。出現(occurence)とブロック(block)に関しては、その項を参照。厳密な定義に関しては、ラムダ計算を知らないと理解が困難だと思われるので、ここでは省略する。
具体例を挙げる。
int x; // 自由変数(1)
int func(int x)
{
cout << x << endl; // 束縛変数(2)
...
}
ここで、(2)の変数xのスコープは、ブロックである関数func内に制限される。関数定義内のオブジェクトの宣言は、ブロックである関数定義の外にスコープが及ばないからである。スコープ(scope)については、その項を参照。
関数をブラックボックスと考えた場合、束縛変数の名前はなんでもよいということになる。関数使用者からしてみると、知っていればよいのは入力と結果(と副作用)だけだからである。従って、xを統一的にyとしてもよい。或はzとしても変わらない。ブラックボックスについては、抽象(abstraction)の項参照。
ところが(1)の自由変数はこのfuncについては束縛されていない。それは、(1)のxがfuncのスコープ外で束縛されているからである。自由変数(free variable)に付いては、その項を参照。
→Free variable 、 Binding、 Scope、 Variable、 Block、 Abstraction、 Free variable
[←先頭へ]| C |
Call-back function
単純にコールバック関数と呼ばれる。イベントハンドラ(event handler)といわれるものの一種である。というか、ほとんど同じものといってよい。
定義としては、簡単に言うと、コールバック、つまり、呼出して戻される関数ということになる。しかし、それでは余りよく分からないので、もう少し詳しく言うと、予め特定のイベント処理用に登録しておいた関数を、システム側(OS側)に呼出させ、その結果を自己が受け取る場合の、その登録しておいた関数のことをコールバック関数という。これをイベンド駆動プログラミング(event-driven programming)に当てはめると、イベントハンドラということになる。イベント駆動プログラミングについては、その項を参照。
なぜこのような一見面倒なことをするのかといえば、イベント駆動プログラミングでは、イベントの発生は通常予測できず、しかも、その処理をプログラム単体で行うには負担が大きいので、それを実行時システム(OS)に管理させることになるが、そうすると、上のような処理をせざるを得なくなるからということになると思う。
図的に理解したい場合は、イベント駆動プログラミングを参照。その図では、実行時システムは、キューの部分とループの部分に相当する。
→Event、 Event-driven programming
[←先頭へ]Closure
閉包と訳されているが、単にクロージャとも言われる。
定義としては、関数適用(式)とそれを評価するのに必要なオブジェクトの環境をパックにしたデータ構造をこのようにいう。thunkと言われるものがあるが、それに似ている。変数は、必要であれば自由変数(free variable)も含まれる。環境(environment)については各項を参照。以下に具体例をあげる。
int b = 2;
int func(int a){
return a + b;
}
int main(void)
{
func(1); /* (1) */
return 0;
}
この場合において、(1)の関数呼出の変数の環境は、a=1とb=2である。従って、ここでのクロージャとは、funcの適用と、a=1とb=2の環境をひとまとめにしてデータ構造にしたものということになる。Coercion
一般的には、強制とか威圧とか言う意味であるが、プログラミング言語においては、型変換と訳されている。
ある型の値を別の型に変換することである。アドホック多相性の一種とも言える。
より狭いものからより広い型に変換することを型拡張(widening)といい、その逆を型縮小(truncation/narrowing)と言う。例えば、整数から実数に変える場合が拡張で、その逆が縮小である。これらの型変換は、型が階層的或は加法的構造を持っているため可能であることが多い。これについては、部分型(subtype)の項を参照。
まだ他にも型変換の類型は存在するが、そういったものは消えていく傾向にあるので取り上げない。
大きく分けて2種類の型変換が存在する。黙示的な型変換と、明示的な型変換である。
多くの型付き言語では、黙示的な型変換がなされている。例えば、以下の例である。
double x;
int y = 5;
x = y * 4.0
この場合、yはintであるものの、y*4.0という式の値は、4.0の型に合わせる形でdoubleとなる。そして、xにはdoubleの20.0が代入される。これは明示的にプログラマが指定するということではなく、コンパイラが意味が通るように自動的に行うため黙示的とされている。Command
単にコマンドとか命令とか訳されている。
いろんな文脈で多様な使われ方をしていると思うが、プログラミング言語に限っていうならば、それは、変数を更新又は制御の流れを変更することのできるプログラムの節であるということになると思う。インストラクション(instruction)とかステートメント(statement)ともいわれることがある。
これは、命令型言語に特徴的な表現形式である。関数型の場合、関数と式(expression)のみで構成されているからである。式については、その項を参照。
これだけでは、ちょっとイメージがつかみにくいと思うので、いくつか例を挙げると、例えば、ジャンプコマンド(break,goto,return等)、代入(assignment)、手続呼出(procedure call)(関数適用ではない)、シーケンシャルコマンド(';'','等)、条件コマンド(if-then-elseのこと)、反復コマンド(for,while,repeat等)、ブロックコマンド("{}",begin-end等)等である。こういったものは、命令型には基本的な要素ではあっても、純粋な関数型にはないことが分かると思う。代入についてはその項を参照。
→Assignment、 Expression、 Abstraction、 Imperative paradigm、 Functional paradigm
[←先頭へ]Constant applicative form
特に訳が見当たらなかったような気がしたので、定作用形などとClean本の翻訳では訳した。一般に、CAFと略される。
定義としては、ラムダ抽象(lambda-abstraction)ではないスーパーコンビネータ(super combinator)のことを言う。ラムダ抽象とスーパーコンビネータに関しては、計算モデルで論じるべきものなので、ここでは説明しない。ただ、それでは結局意味がわからないので、別の言葉で簡単に言うと、リテラルと部分適用関数からなる定数式のことということになるかと思う。ただ、その定数式の中に他の定作用形も含むことができるので、厳密には、リテラル、部分適用関数と他の定作用形からなる定数式のことを言う。リテラル(literal)、部分適用(partial application)と式(expression)に関しては、各項を参照。
その定義からすると、自由変数(free variable)は当然持たないし、ただの変数すらも持たない。自由変数はその項を参照。
CAFの特徴は、常にプログラムのトップレベル(グローバルレベル)に持ち上げることができるということである。持ち上げ(lifting)に関しては、ラムダ計算で説明するのが簡易なので省略するが、CAFの理解の為には、トップレベルに来ることができるということだけ知っていれば十分である。そして、トップレベルに来るということは、存続期間(lifetime)がプログラム実行時間全体に渡り、しかも、シャドウ化されない限りは、全ての通用範囲から参照可能にすることができるということを意味する。更に、定数であるので、プログラム実行中に一回しか評価しないこともできる。従って結局、時間効率性の観点からは大きな利益であるといえるが、領域効率性の観点からは、必ずしもそうとは言えない。存続期間についてはその項を参照。
具体例は特にあげない。簡単にわかるような内容だと思うし、部分適用が命令型の言語では表現しにくいからである。Cleanの翻訳を参照。
→Expression、 Lifetime、 Literal、 Free variable
[←先頭へ]Copy mechanism
訳としては、コピー機構とでも言うのだろうか。
引数機構(parameter mechanism)の1分類で、呼出される関数/手続に、値のコピーを渡し、または返ってくる機構を特にこのようにいうことがある。
これを実現する方法には、大別して3つの方法がある。ここでは、便宜の為に、実引数及び仮引数をそれぞれAP、FPとしておく。
第一に、渡されるAPが値であり、関数が呼出される時にその値のコピーを、生成したFPに代入し、帰る時にはFPが破棄されるだけいうものがある。これを一般に値渡し(call-by-value)という。帰る時には何もされないので、仮引数の値に対する変更は実引数に全く影響を及ぼさない。これはかなり一般的な引数機構であり、C/C++、Pascal等ではデフォルトはこれである。従って、分かると思うので例は挙げない。
第二に、call-by-valueとは対照的なものとして、結果渡し(call-by-result)がある。これは、APが変数又は変数の参照であり、関数呼出時にFPは作られるものの、初期値は定義されず、帰る時になってFPの最終的な値がAPに代入されるというものである。これに関しては例をあげる。
void proc(int &x){ x = 2; }
y = 1;
cout << "y is " << y << endl; // (1)
proc(y);
cout << "y is " << y << endl; // (2)
この場合、(1)ではyは1だが、(2)ではyは2である。というのも、func(y)から返ってくる時に、yには2が代入されるからである。
void print_proc(int &x)
{
cout << "y is " << x << endl;
x = 2
}
y = 1;
print_proc(y); // (1)
print_proc(y); // (2)
この場合、(1)では、"y is 1"となり、(2)では、"y is 2"となる。というのも、(1)のprint_funcの呼出時には、yの現在の値である1がxに代入されるが、(2)のprint_funcの呼出時には、(1)の時にxの最終値たる2がyに代入されているので、yの現在の値は2であり、それがxに代入されているからである。| 名称 | 実引数 | 呼出時の効果 | 戻る時の効果 |
| call-by-value | 値 | APの値を代入 | なし |
| call-by-result | 変数又はその参照 | なし | FPの最終値をAPに代入 |
| call-by-value-result | 変数又はその参照 | APの値を代入 | FPの最終値をAPに代入 |
→Parameter mechanism、 Definitional mechanism、 Evaluation order
[←先頭へ]Correctness
正当性とか、正確さなどと訳されている。正しさなどといっても差し支えないかと思う。
プログラムの実行したいことは通常仕様といわれるが、正当性とは、プログラムがその仕様通りに動作する性質をいう。
正当性を確かめる為には、そのプログラムを適当な値でテストすることも考えられるが、考えられるあらゆる値でそのプログラムが成り立つことを調べることを正当性の証明という。
正当性の証明には、2つのレベルが存在する。それらは各々部分正当性(partial correctness)と全的正当性(total correctness)などと呼ばれている。部分正当性とは、プログラムが終了した場合に求める結果がいつも仕様通りである性質をいう。conditional correctness等ともいわれるらしい。全的正当性とは、部分正当性を満たすプログラムが必ず停止(termination)する性質をいう。因みに、任意のプログラムについて停止性を持つかどうかを確かめるアルゴリズムは存在しないことは既に証明されている。
この他に、正当性には、プログラムを実行しようとする環境で実行できる性質たる実行可能性(feasiability)があげられることもある。
関数型言語では、これらの正当性を証明しやすいとされている。つまり、プログラム自体が等式論理(equational reasoning)で構築されているため、数学上の証明法をそのまま使用できるので、部分正当性を証明しやすい。これについては参照透明性(referential transparancy)を参照。また、遅延評価(lazy evalution)も実現しやすいので、停止性(termination)も満足しやすいのである。これについては評価順序(evaluation order)と停止性(termination)を参照。
なお、正当性にはモジュールの正当性の他に、型の正当性(type correctness)という概念もあるが、これは、プログラムが型に関する仕様を満足している性質をいう。基本的には、型に関する正当性という理解でよい。
→Referential Transparency、 Termination、 Functional paradigm、 Side effect
[←先頭へ]Currying
カリー化と訳されている。Haskell Curryという数学者の名前に因んで付けられている。
一般に、N(N>1)引数を持つ関数は、N-1の関数を返す1引数の関数と定義できる。そこから、1引数の高階関数の適用を連続して使用することで、n(n>1)引数の関数を操作することを言う。Cleanの翻訳を参照。つまり、1引数の高階関数の連続適用による結果は、n引数を持つ関数の結果と同じということである。
ただ、より一般的に、複雑な構造の引数を持つ関数を、連続したシンプルな引数を持つ関数に置換することを言ってもよいと思う。
これは、関数型言語においては、表現力の増大をもたらす。すなわち、より汎用的な関数を引数化することによって、新しい関数を作り出す可能性が開かれるわけである。また、式の中の括弧の数を減らすことができる。それは可読性の増大に貢献する。
| D |
Declaration
宣言などと訳されている。
定義としては、束縛を作り出すプログラムの一部ということになると思う。束縛に関しては、bindingの項を参照。
そして、これの意義については、name等と同様なのでそちらを参照。
ただ、宣言といわれるものの中には、束縛だけではなく、その他に、新しく型や変数を作り出す機能を持つものも存在する。従って、宣言といわれるものの中には、主として2種類ある。
第1は、束縛を作り出すだけの宣言である。これは一般に、定義と呼ばれている。定数定義(constant definition)、変数定義(variable definition)、型定義(type definition)、関数定義(function definition)等における定義は、この意味である。
定数定義と関数定義についてはすぐに分かると思うが、変数定義と型定義については今1つイメージが掴めないかもしれないので、ここで別途解説する。
変数定義とは、既存の変数に、ある識別子を束縛するものである。この機能を認めている言語は多くないが、例示するとすれば、参照渡し(call-by-reference)における仮引数と実引数の束縛がこれに相当することになると思われる。従って、この定義に関する問題については、参照渡しと重なるので、definitional mechanismの項を参照。
型定義とは、既存の型に、ある識別子を束縛するものである。C言語的には、typedefがこれに相当することになると思う。ただ、typedefは、以下の第2の意味でも使用されることが多い。とりあえず、例を挙げる。
typedef int yetanother; /* 型定義 */
typedef struct { ... } some_type; /* 新規型宣言 */
第2は、束縛するだけではなく、同時に、新しい型や変数を作り出す機能を持つ宣言である。これも一般的には型宣言(type declaration)、変数宣言(variable declaration)(初期化も含むことがある)と呼ばれるが、厳密に言うと、新規型宣言とか新規変数宣言と言うべきものである。新規型宣言の例は、C/C++では、structやclass等、Pascalでは、type、Javaでは、classがこれに相当することになる。新規変数宣言の例は、命令型言語には非常に一般的なものなので例示するまでもないと思うが、C/C++、Javaでは、Int、double等であり、Pascalでは、varである。これらについては、特に例は不要だと思うので省略する。→Definitional mechanism、 Binding、 Name、 Anonymous function
[←先頭へ]Definitional mechanism
引数機構の1分類で、定義機構とでも訳すべきだろうか。
実引数が直接に仮引数に束縛される仕組みをこのように言う。
これを実現する方法には、バインドする対象の観点から、constant parameter、variable parameter(call-by-reference)、procedural(functional) parameterの3つに分けることができる。
第1に、constant parameterは、実引数は値又は変数で、関数呼出時に仮引数に直接束縛されるが、関数内での代入などは認められていない場合をいう。まあ簡単に言えば、定数を渡している場合である。これはC言語、Pascal等では、constを付けた場合に似ていなくもないので、特に説明はしない(正確には、constは、call-by-constant-valueといわれるもので、変更できない値を渡すことなので注意)。
第2に、variable parameterは、実引数は変数の参照で、関数呼出時に仮引数が実引数の別名として設定される場合をいう。従って、仮引数の参照又は変更は、実引数の間接参照又は変更になる。C++等では、参照渡し(call-by-reference)といわれるものがこれに相当する。C言語では、これはできないが、ポインタ渡しによって代用している。
第3に、procedural(functional) parameterは、実引数が関数抽象であり、関数呼出時に仮引数が実引数をリネームする場合をいう。従って、仮引数の呼出は、実引数の間接呼出ということになる。C/C++では、関数ポインタを渡すことがこれに相当する。
このような定義機構は、copy mechanismに対して、コピーを作成していない分、一般的にはより効率的であるという特徴がある。ただしかし問題点がないわけではない。それは別名を認めてしまうので、プログラムの可読性が低下するということである。例を挙げる。
void proc (int& x, int& y)
{
y = 1; // (1)
y = x + y; // (2)
}
a = 2;
proc(a,a);
この場合、通常であれば、a=3になったと思うだろうが、実際には、a=2である。というのも、xとyの両方ともがaの別名である為に、(1)の代入において、xとyの値が共に1になってしまうからである。しかし、この結果はソースコードの文面を見る限りでは想定しにくい。これは簡単な例なので、すぐに分かるかもしれないが、例えば、a = {1,2,3,4,5...}であり、proc(a[i],a[j])とした場合だと混乱の度が増すのではないだろうか。つまり、iとjが同じ値になる場合とそうでない場合とでは想定される結果が全く異なったものになってしまうのである。→Copy mechanism、 Parameter mechanism、 Referential Transparency、 Declaration、 Side effect、 Application
[←先頭へ]Deforestation
訳語は特に知らない。一般的には(森林)伐採だが、一部では切り払い等と訳されている。
プログラミング言語の意味にある程度関わることもあるが、基本的には実装レベルの話なので、論じることを要しないとも思ったが、関数プログラミングの世界では比較的知られた概念なので、知っておく優先度は高いと思われる。
この言葉は、通常上記のように森林伐採を指す語として使用され、地球環境問題の文献などでは良く出てくるが、プログラミングの世界ではかなり異なった意味で使用される。即ち、deforestationとは、中間データ構造除去を目的としたプログラム変換(transformation)アルゴリズムのことを言う(変換についてはその項参照。)。モジュラー的に作られた関数プログラムから中間データ構造を除去するプロセスとも言うことができるだろう。
関数プログラミングは、諸仕事を関数単位に分割してプログラムを構成して行くので、非常にモジュール性の高いプログラミングスタイルである(モジュール(module)についてはその項参照。又、関数プログラミングのモジュール性の高さについては、"なぜ関数プログラミングが重要か"を参照のこと。)が、モジュラーなプログラムは大抵中間データ(モジュール間を繋ぐデータ)を作り出す傾向がある。モジュラりティ(modularity)が中間データによって達成されることが多いというべきかもしれない。それは中間データがモジュール間の糊として機能するからである。しかし、中間データ構造は、プログラミング言語が正格であるかそうでないかに関わらずコストのかかる存在と言うことができる。最終的な結果として現れることがないのに、記憶領域を一時的に割当てられ、操作され、そして廃棄されるからである。これは上記の如くモジュール性を求めた結果とも言えるわけだが、このことが関数プログラムを非効率なものにしていることは否めない。そこで、このようなモジュール性と効率性のトレードオフを軽減するべく、ソースコートレベルでプログラムを変換し、中間データ構造を除去することによって効率性を向上させようとした。それがdeforestationである。
ここで、最適化技術について少し説明しておく。最適化技術とは何かを説明する必要はないと思うので、ここではその分類について説明する。一般に、最適化技術は以下の3つのレベルに分類できる。
@ソースコードレベル
A中間コードレベル
Bマシンコードレベル
@はソースコードを自動で変換することによってより効率的なコードを作り出し、それを元にオブジェクトコードを作り出そうとするものである。Aはソースコードとオブジェクトコードの間に中間コードを設定し、そのレベルで最適化しようとするものである。Bはオブジェクトコードになってからそれを最適化しようとする技術である。そしてdeforestationは、この@の最適化技術に分類される。関数型のプログラムは、その参照透明性(referential transparency)故にソースコードレベルの最適化(又はプログラム変換)がやりやすいといわれている。実際にそう言い切れるかは何とも言えないが、理屈の上ではそのように言える。参照透明性についてはその項を参照。
具体的にdeforestationがどのようなものであるのかを簡単な例で説明する。
まず、f(x)とg(x)という関数がある場合に、それを合成する場合である。
g(f(x))
h(x)
ここで、g.fと同じ機能をもつ関数hを使用すれば、得られる値は同じである。そして、hの方が効率が良い。前者の場合、f(x)の結果を出力し、その結果をgの引数として入力し、その後破棄することになるからである。これを中間データ構造(仮想(virtual)データ構造とも言う。)というが、このデータの生成・操作・消滅の分だけ効率性の面でロスがあることは明白である。ここで、g.fをhにするプロセスをdeforestationという。
// mからnまでの整数リストを生成
int *fromTo(int m, int n);
// lstリストの各要素をfuncに適用した結果のリストを出力
int *map(double (*func)(double n), int *lst);
// lstリストの各要素の総計を出力
int sum(int *lst);
// (1)
double func(int m, int n){
return sum( map( sqrt, fromTo(m,n)));
}
これは、fromToで作られた数値リスト要素にsqrtという平方根を求める関数を適用した結果リストに、sumというリスト要素の総計を求める関数を適用したものである。このプログラムは、以下のプログラムに比べれば遥かにモジュラーで明確である。
// (2)
double func(int m, int n){
if(m > n) return 0;
else return (sqrt((double)m) + func(m+1,n));
}
しかし、(1)は(2)に比べるとかなり非効率である。というのも、(1)にはfromToとmapによって作られた結果リスト(中間データ)があり、それをsumに適用しているのに対して、(2)ではそのような作業がないからである。それら結果リストは、fromToによって作られたものがsqrtに適用され、mapによって出力され、sumに適用されるが、その後に廃棄される。この一連のメモリ操作プロセスは非常にコストのかかるプロセスである。(再帰を使わない方が効率的だという議論は、ここでは論議の対象ではない。それについては末尾再帰(tail recursion)の項参照。)Destructive update(assignment)
破壊的更新と訳されている。題にもある通り、破壊的代入も同じ意味である。
ある記憶領域の値を破壊して、他の値に変更することを言う。上書きすることといってもよい。
命令型言語で書かれたプログラムは基本的に、変数の破壊的更新によって成り立っている。これがなければコマンドの並びをつながりのあるものにできないからである。詳しくは、命令型パラダイム(imperative paradigm)参照。
これの最も典型的な操作は、代入(assignment)である。これについては、その項を参照。
破壊的更新は、プログラミング言語に様々な問題点をもたらす。これについては、副作用(side effect)と参照透明性(referencial transparency)の項を参照。
ただ、それらの項でも述べているように、破壊的更新は、計算機にとって見れば、非常に有効な操作であるとも言える。それは、外界とのやり取り(ファイル操作、オブジェクト操作(GUI操作)、プロセス制御等)を行う為であり、或は、状態(口座の残高等)をモデル化するためである。SICPの3.1も参照。
ここでは具体例として、ファイル操作をあげる(エラー処理は省略)。"file"に文字列"aa"を一文字ずつ書込み、表示したいとする。
まず、ある関数を仮定する。つまり、一文字とFILE型のオブジェクトを引数として取り、ファイルの最後にその文字を追加し、ファイル内容を結果として出す関数である。
char *appended(char c, FILE *fp);
そして、以下のようなプログラムがあるとする。
int main(void)
{
FILE *fp = fopen("file","r+");
printf("%s\n",appended('a', fp)); /* (1) */
printf("%s\n",appended('a', fp)); /* (2) */
fclose(fp);
return 0;
}
ここでまずappendedが副作用(破壊的更新)を持つものとする。また、fileは何も入っていないファイルとする。すると、(1)ではfileに文字'a'が書込まれ("a"が表示される)、(2)ではその書込まれたfileに更に別の文字'a'が書込まれる("aa"が表示される)。つまりこのプログラムは、(1)と(2)が全く同一の関数呼出であるにもかかわらず、結果が違う。それは、appendedではfileの破壊的な更新がなされているからである(副作用があるということ)。→Assignment、 Imperative paradigm、 Referential Transparency、 Side effect
[←先頭へ]Dynamic binding
動的束縛とか単に動的バインディングなどと訳されている。dynamic scopingともいう。
定義としては、静的束縛(static binding)と対を成すが、ある名前がどのオブジェクトにバインドされているのかがプログラムテキストからは直接には分からない束縛をいう。このために、実際のバインディングを実行時まで延期しなければならない。つまり、プログラムの動的な制御の流れに依存するのである。オブジェクト指向的に読み直すと、実行時において、メッセージ受信後に、そのメッセージと実行されるメソッドが結合されることということになろうかと思う。
具体例を挙げる。
const int x = 0; // (a)
int func(int n) {
return n * x;
}
void proc(void) {
const int x = 1; // (b)
cout << func(5) << endl; // (1)
}
int main(void) {
cout << func(5) << endl; // (2)
return 0;
}
こうした場合、静的束縛では、(1)と(2)の結果は同じ0である。ところが、動的束縛であると仮定するとそうはならない。(1)は5となり、(2)は0となる。つまり、(1)の時点でのfunc内のxは(b)の宣言でバインドされたものを示すのに対して、(2)の時点のxは、(a)の宣言でバインドされたものを参照する。結局、静的束縛は関数本体を「関数定義の環境(lexical environment等ともいう)」下で評価する一方で、動的束縛は関数本体を「個々の関数呼出の環境(calling environment等ともいう)」下で評価する(つまり、束縛が実行時に定まる)と言うことができる。→Binding、 Environment、 Scope、 Type checking、 Typing
[←先頭へ]| E |
Environment
環境と訳されている。文脈(context)とも言われる。
プログラミング言語の世界でも開発環境との関連で色々な意味で使用されているが、ここでは束縛(binding)に関連した環境を説明する。
環境とは、束縛の集合或はその記憶である。束縛については、その項を参照。識別子の集合から実体の集合への部分関数といっても良いかもしれない。あらゆる式やコマンドは特定の環境下で評価され実行される。従って、異なった環境では式やコマンドの結果も異なってくることになる。
具体例を挙げる。
int x;
void proc(int x)
{
double y;
... // (1)
}
int main(void)
{
... // (2)
double x;
int y;
... // (3)
proc(10)
...
return 0;
}
このような状況下では、(1)の時点の環境は、以下のものになる。→Binding、 Block、 Dynamic binding、 Closure、 Side effect
[←先頭へ]Evaluation order
評価順序などと訳されていると思う。計算モデルのレベルでは、簡約戦略(reduction strategy)と言われる。
評価順序というからには、文字通り式が評価される順番のことだが、関数型言語の場合、プログラムが関数適用と関数定義のみによって成り立っているので、結局、結合性(associativity)と優先順位(precedence)を除くと、関数の実引数(部分項)がいつ評価されるのかという問題だけが残る(複数の条件式の評価順序の問題も残されているが、これは問題といえるような問題ではないので特に考慮の必要は無いと思う)。
従って、評価順序とは、関数の実引数が評価される時期に関する概念である。以下の3つが考えられる。
@eager evaluation (call-by-value)
Acall-by-name
Blazy evaluation (call-by-need)
@は、先取評価とか、先行評価とか訳されるものであり、計算モデルのレベルでは、applicative-order evaluation(reduction)とか、innermost evaluation(reduction)(最内簡約)とか、strict evaluation(正格評価)と呼ばれる。また、data-driven evaluation(データ駆動評価)ともいわれることがある。定義としては、関数適用の一番内側(つまりは、実引数)から評価するというものということであり、値渡しに相当する。つまり、関数呼出し時にまず実引数を評価してしまう(関数本体は、その後に実行される)というものである。従って、仮引数に束縛されるのは、常に実引数の評価結果である。関数定義の中に出現している仮引数には全て、この評価結果が代入されることになる。call-by-valueについては、copy mechanismの項を参照。
Aは、名前渡しとか名前呼び等と訳され、Bは、遅延評価(必要渡し)等と訳される。これらは、normal order evaluation(reduction)(正規順序簡約)とか、leftmost-outermost evaluation(reduction)(最左最外簡約)とかnon-strict evaluation(非正格評価)と呼ばれる。遅延評価に関しては、更にdemand-driven evaluation(要求駆動評価)とも呼ばれたりする。定義としては、関数適用の一番外側の一番左側から評価するというものということである。これは結局簡単に言うと、実引数が関数定義の中で仮引数の出現があった時に評価されるということになる。従って、仮引数に束縛されるのは、実引数それ自体である。関数定義の中に出現している仮引数には全て、(評価されていない)実引数それ自体が代入されることとなる。出現(occurrence)については、その項を参照。
@とA・Bの評価の違いを以下に例示する。
int func(int a){
return a * a;
}
...
func(5+2); /* (1) */
*****************************************
値呼びの場合
func(5+2)
=> func(7)
=> 7 * 7
=> 49
名前呼びの場合
func(5+2)
=> (5+2) * (5+2)
=> 7 * (5+2)
=> 7 * 7
=> 49
以上のように、(1)の関数適用の評価プロセスは、値渡しと名前渡しでは違う。値渡しの場合は、最初の実引数の5+2の評価を済ませてから関数本体の評価に入る(つまり、式func(5+2)の中で最も内側にある5+2から評価している)が、名前渡しの場合は、まず本体の評価を行い、その過程で仮引数の出現が式の中の一番外側に来た場合に実引数を評価している(つまり、まずはfuncを評価すると、a*aとなるので、次にこれを評価するが、式a*aの中で一番外側にあるのはa(ここでは5+2)なので、ここではじめて実引数を評価する)。
/* funcは停止しない関数であればなんでも良い */
int func(void);
{
while(1);
return 1;
}
int sum(int a, int b)
{
int s;
s = a + a;
return s;
}
...
sum(1,func()); /* (1) */
この場合において、先行評価を採用すると、(1)の関数呼出は終了しない。というのも、func()は無限にループを続け、実行がいつまでも帰ってこないからである。ところが、遅延評価であると仮定すると、(1)のfunc()は、(1)の関数呼出において一度も使用されないので、評価されることがなく、従って、(1)は正常に仕事を果たし終了することとなる。停止性を満足する場合が多いことの利点に付いては、停止性(termination)と正当性(correctness)の項を参照。
int f(int a) {return a*a*a;}
int g(int x, int y) {return x;}
...
g( f(3), f(4) );
この場合、先行評価であると、評価ステップは以下のようになる。
g(f(3),f(4))
=> g(3*3*3, f(4))
=> g(27, f(4))
=> g(27, 4*4*4)
=> g(27,64)
=> 27
そして、A・Bの場合は、以下のようになる。
g(f(3),f(4))
=> f(3)
=> 3*3*3
=> 27
これを見て分かるように、先行評価版に比べて、遅延評価版の方が評価ステップが少ないことがわかる。そして、大体想像がつくと思うが、この利点は、(ここではgの)実引数自体の評価にかかるコストが大きく、かつ、仮引数(ここではf)の出現回数が少ない(または無い)場合に特に威力を発揮する。
int f(int a){return a*a*a*}
int g(int a){return a+a+a;}
...
g(f(4));
この場合、先行評価であると、評価ステップは以下のようになる。
g(f(4))
=> g(4*4*4)
=> g(64)
=> 192
これに対して、Bの評価ステップは以下のようになる。
g(f(4))
=> f(4) + f(4) + f(4)
=> 4*4*4 + 4*4*4 + 4*4*4
=> 64 + 64 + 64
=> 192
Aのステップは以下のようになる。
g(f(4))
=> f(4) + f(4) + f(4)
=> 4*4*4 + 4*4*4 + 4*4*4
=> 64 + 4*4*4 + 4*4*4
=> 64 + 64 + 4*4*4
=> 64 + 64 + 64
=> 192
これを見て分かるように、A・Bの場合は、遥かに迂遠な作業を強いられる結果となる。特にAの場合は、その非効率性は明白である。
どのような場合に非正格な評価の方が時間効率性がよいのかについてを、以下の図にまとめて見た。関数の評価の全ての場合が円の中に包含されているものとし、それは、図に示すような順序に従って配置されているものとする。そして、二本の直線が両者の効率性の優劣を分け隔てるボーダーであるものとする。
| 名称 | APの評価時期 | 呼出時にFPに束縛される物 | FP出現後にFPに束縛されている物 |
| call-by-value | 本体評価前 | APの評価結果 | APの評価結果 |
| call-by-need | 本体評価中のFP出現時 | AP自体 | 同 上 |
| call-by-name | 同 上 | 同 上 | AP自体 |
→Parameter mechanism、 Occurrence、 Termination、 Strictness、 Copy mechanism
[←先頭へ]Event
主としてオブジェクト指向における概念だが、関数型言語にも出て来るので説明する。
一般的には、出来事、事件などと訳されるが、プログラミングの世界では、単にイベントと訳されることが多い。ただ、事象とも訳されたりする。
定義としては、プログラムを実行している環境に一定の状態変化を起こす出来事をこのようにいう。例をあげると、マウスの移動、クリックやキーボードによる入力等である。
これが果たす役割に付いては、イベント駆動プログラミング(event-driven programming)を参照。
→Event-driven programming、 Call-back function
[←先頭へ]Event-driven programming
イベント駆動プログラミングと訳されている。単にイベントドリブンプログラミングとも言う。
定義としては、イベント(event)の発生を待つループが存在し、イベントが発生すれば、それに関連付けられた処理手続(event handler)が実行されることを特徴とするプログラミングスタイルをこのように言う。event、event handlerとプログラミングスタイルに付いては、event、call-back function、paradigmの各項を参照。具体的には、以下の図を参照。

→Event、 Paradigm、 Call-back function
[←先頭へ]Expression
式とか表現とか訳されている。
プログラミング言語に限っていうならば、評価されるもの、つまり、値を返すものということになると思う。もっと単純に、演算とオペランドの列だといってもよいかもしれない。(純粋な)関数型的に言うと、関数適用のことといってよいだろう。
これだけでは何の事かわからないかもしれないので、具体例を挙げる。
例えば、リテラル(数字の3とか"Hello"等の文字列それ自体)、関数呼出(function call、当然演算子も含む)、条件式(C言語の(x > y) ? x : y等、条件コマンドとは違う)などである。
これは、命令型にも多く存在するものだが、純粋な関数型の場合は、これしかない。他の、例えば、コマンド等は存在しない。
ただ、念の為に言っておくと、多くの命令型言語では、式とコマンドを余り明確に区別せずに論じている場合がある(例、代入式(assignment expression)、実質的には手続と言える関数呼出等)。
→Command、 Abstraction、 Functional paradigm、 Literal、 Constant applicative form、 Lambda
[←先頭へ]| F |
First-class object
第1級オブジェクトなどとも訳されるが、単にファーストクラスオブジェクトとも言われる。
定義としては、数学的オブジェクトとしての権利を全て取得しているオブジェクトのことである。詳細には、以下の能力を持つプログラム言語要素のことである。
1.オブジェクトに名前がつけられる。
2.引数として関数・手続に渡すことができる。
3.結果値として関数から帰って来ることができる。
4.データ構造に組み込むことができる。
通常は、単純なデータ又はデータ構造しかこのような身分を持つことはないのだが、関数型言語では、関数にもこのような身分を持たせている。
このことの持つ意義については、高階関数(higher-order function)を参照。
→Higher-order function、 Anonymous function
[←先頭へ]Free variable
自由変数と訳されている。
束縛変数(bound variable)と同じように、論理学における概念と同じであり、関数型言語ではラムダ計算で使用される概念である。
あるブロックについて自由な出現(free occurence)を持つ変数のことを言う。出現については、その項を参照。つまり、そのブロックによっては束縛されていない変数のことである。関数プログラミングとしては、関数引数ではないがその関数によって参照される変数のことである。ある意味では、大域変数ともいえるが、深くネストされたスコープのあるプログラムでは、全てがそうであるわけではない。具体例については、束縛変数参照。
この変数の存在は、プログラムにとって必ずしもよいものではない。というのも、関数や手続等の呼出し毎に様々な意味を持ち得ることを意味するからである。これは、モジュールの参照透明性(referential transparency)を壊す元になる。参照透明性はその項を参照。
→Bound variable、 Referential Transparency、 Binding、 Occurrence、 Constant applicative form、 Closure、 Variable
[←先頭へ]Functional paradigm
関数型パラダイムとでも訳すことになるのだろうか。訳としてはそういうもので良いと思う。
関数型のプログラミングスタイルのことを言う。つまり、式(expression)と純粋関数のみで成り立っているプログラミングのことである。式と関数については、式と抽象(abstraction)の項を参照。変数の値の変更を伴わない(副作用がない)プログラミングと言ってもよいと思う。計算の基本単位が関数適用であるプログラミングともいえるかもしれない。
これは、命令型言語と比較してみれば明らかになることだと思うので、命令型パラダイム(imperative paradigm)の項を参照。
ただ、ここでもいくつか説明しておく。プログラムというのは一般に関数であるということができる。つまり、ある入力値を取り結果値を返すものがプログラムであるということができる。このような考えに立つ場合、命令型言語の場合は、この関数がコマンドによって間接的に実現されているといえる。つまり、@入力値の読込、A操作、B結果の出力ということが、記憶領域に保存されている変数の変更によって媒介されている。具体例としては、命令型パラダイムの項を参照してもらえれば分かると思う。コマンド間の関係が変数によって仲立ちされているのである。これに対して、関数型の場合はそのようなことがなく、関数としてのプログラムを直接的に表現している。つまり、プログラムが関数の集合によってのみ成り立っているので、@関数1の入力、A関数1の出力=関数2の入力、B関数2の出力=以下続く...というように、関数間の関係が変数によって媒介されるということがない。
そのような副作用を招く変数の存在がないことから、関数プログラミングの要素たる関数は参照透明性(referencial transparency)という重要な特徴を有する結果になった。これに関しては、その項を参照。また、副作用(side effect)の項も参照。
そして、以上のことから、標語的に、関数型言語のプログラムは、どのようになされるべきかではなく、何がなされるべきかについて記述するものであるといわれている。その意味から、関数プログラミングは、宣言的プログラミング(decralative programming)のサブクラスともされる。関数型言語のプログラムは、実行可能な仕様なのである(理想的には)。
一応具体例を示しておく。階乗関数を示す。まずは命令型。
function factorial(n : integer) : integer;
var product : integer;
begin
product := 1;
while n > 0 do
begin
product := product * n;
n := n - 1
end;
factorial := product
end;
次に関数型。一応Pascalで表現するが、一般に命令型言語では、このような表現は効率の悪いものとして忌避されている。しかし、関数型言語ではこれを極力効率的なやり方で実装しているので、命令型言語で関数プログラミングを行う場合よりも遥かに最適化されていることを指摘しておく。
function factorial(n : integer) : integer;
begin
if n > 0 then factorial := n * factorial(n-1)
else factorial := 1
end;
関数型パラダイムの、命令型パラダイムと比較した場合の特徴は、まず上述したように宣言的であるということである。それは、(解決すべき問題の仕様から実際に解決するプログラムへの)変換とプログラムの正当性の証明が容易であるということをもたらす。これに関しては、参照透明性や正当性(correctness)も参照。→Abstraction、 Expression、 Imperative paradigm、 Correctness、 Command、 Paradigm、 Referential Transparency、 Lambda
[←先頭へ]| H |
Higher-order function
高階関数と訳されている。数学で学ぶ内容と同じといってよいと思う。
つまり、他の関数を引数として、又は結果値として扱うことのできる関数である。これに対して、データしか扱えない関数を一階関数(first-order function)という。
このような関数があることは関数型言語の大きな特徴とされている。というのも、この可能性によって、飛躍的に表現力が高まるといえるからである。具体的に例を示すというと長くなるので、Cleanの翻訳やSICPを参照。まあ、一般的にいうと、アルゴリズムをよりモジュール化できるということになるということになろうかと思う。具体的な関数の例としては特に、map、filter、fold関数などを見てもらえれば分かると思う。
また、これを扱えることから、関数合成(functional composition)、部分適用(partial application)やカリー化(currying)の可能性も開けてくる。詳しくは各々の項を参照。
高階関数では、関数それ自体が第1級のオブジェクト(first-class object)である。これに関しては、その項を参照。
関数ポインタ等を知っている人にとっては、それとどう違うのかと思う人がいると思うが、基本的には役割が違うということになるだろうか。関数型言語では、関数の集合によってプログラムが成り立っているので、自然に高階関数が表現力の増大に大きな貢献をするが、命令型言語の場合は、関数ポインタは多相性の文脈で語られる程度で、表現力の増大という観点からは関数型ほど大きな貢献をするというわけではない。また、関数ポインタはその名の如く、やはりポインタという形を経由してしか高階関数を実現できないので、他のデータと同じように扱うことで表現力の増大を図ろうとするものとは違うと言える。
→Currying、 First-class object、 Partial application
[←先頭へ]| I |
Imperative paradigm
命令型パラダイムと言われている。このように略さず、imperative programming paradigmともいうことがある。パラダイムに付いては、その項を参照。
imperativeというのは、ラテン語のimperareから来ていて、それは「命令する(to command)」という意味である。
定義としては、簡単に言うと、記憶領域に保存される変数の更新を行うコマンドの並びに基づいて成り立つプログラミングスタイルのことである。コマンド(command)については、その項を参照。また、フォン・ノイマンのアーキテクチャを維持したスタイルと言ってもよい。つまり、言語のステートメントの逐次的実行及び変数の変更が、そのままハードウェアのマシン命令の逐次的読取・実行及び記憶領域の変更を反映しているスタイルである。
このスタイルに基づく言語が現在のソフトウェア開発の主流を占めている。その理由には、主として2つあると考えられる。
第一に、マシンのハードウェアレベルの構成と非常に近い対応関係を持っていることがある。つまり、現在のコンピュータは、基本的に機械語命令によるメモリのフェッチ及び更新によってその動作を実現しているので、それに非常に近い対応関係を持つ命令型言語は、シンプルにしかも効率的な言語プロセッサを実現しやすいのである(ただ、現在では、ことによると命令型より高速な実装も登場しているので、シンプルさではやはり負けるが、効率性に関しては一概には言えなくなってはいる)。
第二に、関数型言語からしてみるとこちらの方が重要だが、時間に沿って状態を変化させる実世界のオブジェクト(銀行口座、飛行機、車、人等)をモデル化することが非常に自然にしかも簡単に行えるということがある。オブジェクト指向は、命令型のこの特徴を最大限生かそうとするものであるといえる。破壊的更新(destructive update)も参照。
ただ、このような長所ばかりではない。
第一に、計算の中間結果の保持に変数を使用し、その変数の変化を通じて計算を実現しようとする所にある。つまり、コマンド間の関係が、両者がアクセスできる変数を仲立ちにして形成されるということである。
具体例を挙げる。SICPの1.2も参照するとよい。
aからbまでの総和を求めたいとする。
function sum(a : integer; b : integer) : integer;
var product, i : integer;
begin
product:=0;
if a>b then sum:=0;
else begin
for i:=a to b do
product := product + i;
sum := product
end
end;
このように命令型のプログラミングスタイルであると、コマンド等(for、+、writeln等)は単に並んでいるだけで互いに何の関係も持っていないので、その仲立ちとして、反復の制御変数i、その時々の成果(中間結果と最終結果)の変数productが必要になってくる。それを通じてのみコマンド間の関係は維持され、そして一連のプログラムができあがる。
function sum(a : Integer; b : Integer) : integer;
begin
if a>b then sum:=0;
else sum := a + sum(a+1,b)
end;
こうすれば制御変数と結果の変数を必要としない。それは、式又はコマンドが状態変数を経由せずに直接に繋がっているからである。両者を比べてみれば、どちらが簡潔に表現されているかは明らかであるかと思う。前者の例では、全ての計算を状態操作に変形することによって、つまりここでは漸化式を展開することで成り立っているが、後者はそうではない。そのまま表現している。このことで、前者の場合には、アルゴリズムの仕組みそれ自体の他に、変数の値が時々の場所でどのような状態を持つかに気を払わなければならない。この為の変数がどんどん増えていけばそれだけ複雑さも増す。→Command、 Referential Transparency、 Paradigm、 SICP、 Functional paradigm、 Destructive update(assignment)
[←先頭へ]Infinite data structure
無限データ構造と訳されていると思う。
定義は難しいものではない。つまり、要素が無限数あるデータ構造のことをこのようにいう。
通常、計算機のリソースの有限性から、データ構造の要素は有限であるが、遅延評価(lazy evaluation)により一部分しか評価しないことによってデータ構造の要素を無限数あるかのように表現できる。従って、実際に無限数要素を持つデータ構造をそのままの形で表現できるわけではない。ただ、それによって要素が無限数あるデータも処理できるので、プログラミング言語の表現力増大にとっては重要である。遅延評価に付いては、Evaluation orderを参照。
具体例は後日追加する。
| L |
Lambda
ラムダと発音する。ギリシア文字のλ(小文字)とΛ(大文字)の英語の綴りである。これ自体は、何ら特に意味のないギリシア文字の1つに過ぎないのだが、ラムダ計算(lambda-calculus)といわれる計算モデルの中では、特別の意味を持つ記号として使用されている(計算モデル(computation model)については、その項を参照)。つまり、ラムダとは、ラムダ計算の中で使用されるラムダ式(lambda expression)を表す記号である。
ここで、ラムダ式とは何かを説明しておくと、ラムダ式とは、ラムダ項(lambda term)とも言われ、ラムダ計算の中での基本的な計算単位である。具体的には、ラムダ抽象(lambda abstraction)、適用、変数又は定数のいずれかである式のことである。ラムダ抽象とは、無名関数を定義できるラムダ式のことで、通常の(関数)プログラミング言語でいうと、関数定義に相当する。無名関数と関数定義については、anonymous functionの項と、abstractionの項を参照。適用とは、ラムダ抽象の適用のことで、関数適用に相当することになる。BNF的に表現すると、以下のようになる。
Lambda式 = ラムダ抽象 | 適用
| 変数 | 定数;
Lambda抽象 = '(' 'λ' 変数 '.' Lambda式 ')';
適用 = '(' Lambda式 Lambda式 ')';
詳細に知りたければ、ラムダ計算の本を読むとよいのではないかと思う。関数型言語の計算モデルでもあるので、関数型言語の意味内容が非常に分かるのではないかと思う。
→Abstraction、 Expression、 Functional paradigm、 Anonymous function
[←先頭へ]Lifetime
生存時間とでも訳すべきか。storage durationとも言われるので、記憶期間としてもよいかもしれない。
定義としては、簡潔に、変数の発生から消滅までの期間をいう。
生存時間という概念を認める実益は、効率性に帰着する。つまり、変数は必ず生存している間一定の記憶領域を占拠しているので、使われなくなった場合には、それを依然として残しておくことは記憶領域の無駄ということになる。従って、プログラム実行中全体に渡って全ての変数を存続させる必要の無い場合には、適当な時点で破棄されるようにした方がよい。その観点から、変数はブロック内でのみ生存時間を持つという手法が広く行われている。
例を挙げる。
// ここから
int x = 1; // (1)
int main(void)
{
int y = 10; // (2)
for(int i=1;i<10;i++) { // (3)
...
}
proc();
return 0;
}
void proc(void) {
int z = 20; // (4)
...
}
// ここまで
以上のような例では、x,y,z,iの生存時間は以下のようになる。
→Block、 Constant applicative form
[←先頭へ]Literal
単にリテラルと訳されている。即値や直定数と訳されることもある。
通常の意味は、「文字通りの」とか「ありのままの」とかいう意味だが、プログラミング言語の世界では、特定の型の不動で明白な値を表す、最も単純な式を言う。式(expression)に関しては、その項を参照。つまり、評価後もそのままの形で残っている式である。あるいは、出現する場所とは関係なく常に同一値を出力する式といっても良いかもしれない。これは簡単だと思うが、一応例を挙げておく。
24 -> 整数リテラル
24.5 -> 実数リテラル
'C' -> 文字リテラル
"String" -> 文字列リテラル
→Expression、 Constant applicative form
[←先頭へ]| N |
Name
そのまま名前と訳されている。
オブジェクト自体ではなくそれの名前のことである。
主として、プログラムの可読性を高める為にあるといえる。
別の名前として、識別子(identifier)がある。どちらでも基本的には同じ意味である。
→Namespace、 Declaration、 Variable、 Anonymous function
[←先頭へ]Namespace
名前空間と訳されている。あるいは単にネームスペースという場合もある。
定義としては、全てが一意に定まる名前の集合ということでよいと思う。名前(name)に付いては、その項を参照。あるいは、名前とオブジェクトの対応関係が一意に定まる範囲と言ってもいいかもしれない。
通用範囲に非常に近い存在であり(というより、ほとんど同じ)、実際、通用範囲規則は、ある名前がどの名前空間に属しているのかを決める。通用範囲(scope)に付いては、その項を参照。また、クラスの名前に果たす役割にも類似する。
名前空間は、一般に、プログラムの論理的な構造を明確に表現する為に認められた機能である。端的にいえば、(特に大規模プログラム開発において)名前の競合を防ぐ為であり、少し詳しく言うと、あるライブラリ(或はモジュール)が他のライブラリから独立している(モジュラーである)ことを保証し、可読性、再利用性やメンテナンス性を高めることを助ける為に認められた機能である。モジュール(module)に関しては、その項を参照。
名前空間は、昔の言語であると1つしかないことが多いらしいが、例えば、C++等では、原則として複数存在する。
名前空間に関しては、Bjarne氏のC++の本が読みやすいと思われる。
| O |
Occurrence
一般的には、生起、発生、出来事等とも訳されるが、プログラミング言語の文脈では主として出現と訳されている。それは、論理学の概念から輸入しているからである。
まず、論理学における出現とは、項や論理式の中に現れる位置のことをいう。これに関しては、論理学を参照してもらうほかないが、プログラミング言語でも大体同じような意味である。つまり、あるオブジェクトが式の中に現れる位置のことである。
例を挙げる。
int func(int x) // (1)
{
...
cout << x << endl; // (2)
...
}
この時の(1)や(2)のことをxの出現という。まあ、出現という言葉の語感からもすぐにわかってもらえるとは思うので、出現自体の説明はここで終わる。
int x; // (1)
void func(int y) // (2)
{
...
cout << x << endl; // (3)
cout << y << endl; // (4)
...
}
ここで、funcのブロック内だけを考える。すると、(2)でyがintとして宣言されている。これは、yの束縛する出現である。yを制約し、特定のオブジェクトに束縛するからである。対して、(4)のyは、束縛される出現である。funcのブロック内で束縛された識別子だからである。つまり、束縛する出現と束縛される出現は、あるブロック内の宣言とその識別子の出現のことをいう。ところが、(3)のxは、(1)というfuncの外部で束縛されている。従って、funcに関しては、xは自由な出現である。つまり、(3)のxは、funcに関しては束縛することもないし、束縛されてもいないのである。これをoccur freeと言ったりする。→Binding、 Free variable 、 Evaluation order
[←先頭へ]| P |
Paradigm
訳としては、理論的枠組であるとか、方法論又は単にパラダイムとされている。場合によっては、プログラミングスタイルと呼ばれることもある。
定義としては、プログラミングスタイルそのままに、プログラムを構成する形のことと言ってよいと思う。
パラダイムは、2つの意味で使用されることがあるように思われる。第一に、計算モデルに対応した理論的枠組であり、第二に、プログラム構成法というような意味合いで使われる場合である。計算モデル(computation model)については、その項を参照。
第1の観点からすると、プログラミングには大別して3つのパラダイムがある。即ち、命令型(imperative)(手続型(procedural))、関数型(functional)、論理型(logical)である。それに対応する具体的な言語には、命令型として、C/C++,Pascal,Java,C#等があり、関数型には、Scheme,Lisp,ML,Haskell,Clean等があり、論理型には、Prologがある。命令型は、フォン・ノイマン型(von Neumann)とも呼ばれる。また、関数型、論理型は総称して、非手続型とか、宣言型(declarative)とか言われる。個々のパラダイムについては、各項を参照。これらパラダイムは、1つの言語内、或は、1つのアーキテクチャ内では、原則として共存できない(整合しない)とされる。
第2の観点からは、いくつかのプログラミングスタイルが考えられる。即ち、手続プログラミング、関数プログラミング、抽象データ型プログラミング、モジュールプログラミング、オブジェクト指向プログラミング、汎用プログラミング、宣言型プログラミング等である(並行プログラミングもあり得るが、ここではそうしない)。これらのパラダイムは、1つの言語内で共存可能なものが多い。例えば、C++は、このうちの、かなりの部分をカバーしている。また、ML等も、関数型の他、汎用プログラミング、抽象データ型プログラミング等もカバーする。このうち、オブジェクト指向型を特にデータ主導型、手続型、関数型、宣言型等を計算主導型と言ったりもする。これら全てのプログラミングについては、説明しない。少なくともC/C++等を学んでいれば分かるような内容だからである。
ただ、関数プログラミングに関連して、ここでは、手続型と非手続型(宣言型)の区別を簡単に説明する。
手続型のプログラムの記述には、プロセッサがどのような操作をするのかについてのステップ単位の記述を必要とするということと、その実行順序も正確に記述しなければならないという特徴がある。一方で、非手続型は、基本的にはプログラムを数学的記述と捉えるものという特徴づけができる。つまり、詳細な実行を記述する必要がない。標語的にいわれているが、手続型のプログラムは、どのように(how)実行されるべきかが記述されているのに対して、非手続型は何が(what)実行されるべきかが記述されている。
現在命令型言語が主流である。それ理由は至って単純で、現在のコンピュータのハードウェアの構成がチューリングマシン互換だからである。つまり、マシン語とプログラム言語とのかなり近い対応関係が成り立つためである。また、シンプルでもある。そして、効率性の観点からも、入出力等の処理の観点からも優位性をもたらしている。しかし、宣言的なプログラムの方がプログラムの記述と言う観点から望ましい属性を持っていることは事実であり、その弱点を克服する試みが色々なされている。
→Imperative paradigm、 Functional paradigm、 Event-driven programming
[←先頭へ]Parameter association
訳は特に知らない。調べてみたが分からなかった。多分、引数結合とでも訳すことになるのだろうか。parameter correspondenceともいう。
引数機構の1分類で、仮引数と実引数の照合方法に関する概念である。実現方法としては、大別して2つの方法が考えられる。
第1に、位置的照合(positional association(correnspondence))といわれるもので、関数が呼出されると、コンマ又はスペースで区切られた実引数を順序通りにリストするというものである。これは、実引数の順序、型が仮引数のそれと一致していなければならないということを意味する。従って、省略したい実引数がある場合には、その部分にプレースホルダを使用するか、デフォルト引数を予め指定しておかなければならない。この照合方法の特徴は、未定義の実引数リストを取り扱うことができるということである。例えば、C/C++のコマンドライン引数等がその具体例である。この照合方法はかなり一般的で、C、Pascal、Javaなどほとんどの言語によって採用されている。
第2に、キーワード照合(keyword(name) association(correspondence))といわれるもので、位置ではなく、仮引数と実引数の名前の一致で照合するというものである。これは、実引数の順序が仮引数のそれと一致していなくともよいということを意味している。また、省略したい実引数がある場合にも問題なく行うことができる。従って、可変数の実引数を取り扱うことができるという特徴がある。また、どの実引数がどの仮引数にバインドされたかが一目で分かるので可読的であるということもできる。これを採用している言語は多くないので、例を挙げる。キーワード結合であると以下のような呼出も認められる。それは、名前の一致でバインドを決定するからである。
int func(char *name, char *address, int age){ ... }
func(age=50, name="anonymous", address="Japan"); // (1)
func(age=50, name="anonymous"); // (2)
位置的照合では、このような呼出は認められないが、キーワード照合では、引数の名前で照合するので、このような実引数の順番を変えた(1)のような呼出も認められる。また、位置で照合を決めてはいないので、(2)のように仮引数より少ない数の実引数で呼出しても問題がない(一致しないものには、通常適当なデフォルト引数が割り当てられる。まあただこれに関しては特徴ではなくなっているかもしれない。)。ただ、この方法では、コマンドライン引数のような引数リストのようなものは作りにくい。
[←先頭へ]
Parameter mechanism
引数機構のこと。つまり、仮引数と実引数の結合方法に関する仕組みのことである。因みに、引数を渡すことは、parameter passingという。
いくつかの観点から多種の引数機構を考えることができる。
第1に、渡すオブジェクトがその評価結果又はそれ自体のコピーである場合である。この観点からは、実現方法として、call-by-value、call-by-result、call-by-result-valueがある。詳しくは、copy mechanismの項を参照。
第2に、渡すものが渡したいオブジェクトそれ自体である場合である。これについては、constant parameter、variable(or reference)parameter、function(or procedure) parameterが考えられる。詳しくは、definitional mechanismの項を参照。
第3に、実引数の評価時期という観点である。これに関しては、eager evaluation、lazy evaluationがある。詳しくは、evaluation orderの項参照。
第4に、引数の照合方法という観点である。この実現方法としては、named parameter associationとpositional parameter associationがある。詳しくは、parameter associationの項を参照。
→Copy mechanism、 Parameter association、 Definitional mechanism、 Evaluation order
[←先頭へ]Partial application
主に部分適用と訳されている。partial parametrization(部分パラメータ化)も同様の意味である。
定義としては、複数の引数を持つ関数において、その引数の最大数よりも少ない数の引数による関数適用のことをいう。例えば、ある関数の引数の数が3だった場合に、それより少ない数、つまり、前から1つ又は2つの引数で関数を呼び出すことをそのように言う。多くの命令型言語においては、これをする能力はない。
このようなものを認める実益は、関数型言語においては、高階関数の表現力を更に高める為である。つまり、例えば、部分適用を引数とした関数を使用できるので、部分的に引数を指定できない場合よりも、可読性や簡潔性が増す場合が存在するのである。
具体例はとりあえず省略する。
→Higher-order function、 Application
[←先頭へ]Partial evaluation
部分評価と訳されている。実装上の技術で、プログラミング言語の意味とは余り関係ないが、関数型言語には重要なので説明しておく。
部分評価とは、最適化技術の一種であり、一定の条件を満たす部分式をコンパイル時に予め評価・変換し、実行時の実行ステップ数を減らす技術をこのように言う。
その一定の条件とは、プログラムの入力(実引数)の変化に依存しないことを言う。
以下によく挙げられる具体例を示す。
int power(int x, int y) {
if(y=0) return 1;
else return x * power(x, y-1);
}
/* (a) */
int func(int a) {
return power(a, 5);
}
**************************************
/* (b) */
int func(int a) {
return a * a * a * a * a * 1;
}
**************************************
/* (c) */
inf func(int a) {
return a * a * a * a * a;
}
この場合、(a)のfuncにとって、powerのyは5という定数であり既知で一定のものである。従って、funcの評価の一部(ここでは、powerの適用のこと)は、その引数であるaには依存していない。そこで、funcは(b)のように変換できる。更に、(c)のように変換できる。
[←先頭へ]
Purity
純粋性と訳されている。一般的な訳と変わらない。
プログラミング言語の文脈では、関数が副作用をもたないことを言う。関数については、抽象(abstraction)の項を、副作用(side effect)についてはその項を参照。
関数型言語の文脈でも大体同様の意味なのだが、「関数が純粋である」と言うより、「純粋な言語」というように使用される。つまり、purely functionalであるとは、(純粋な)関数適用によってのみ計算を行う言語に付けられる形容詞である。それは、副作用もなければ、命令型に特徴的なもの(コマンド等)もないということである。
例えば、命令型的な変数もない。通常、変数というと代入によって時間毎に変化する値を持つものということになるが、純粋な関数型言語では、数学と同じで、その通用範囲内においての未知の定数という意味になる。つまり、極めて静的でダイナミックに変化させられるということがない。
また、副作用もないので、参照透明性が確保できる場合が多い。これについては、参照透明性の項を参照。
以上が純粋性の基本的な内容なのだが、別の意味でも使用される。つまり、副作用があり、破壊的更新が可能でありながら、関数が、実引数が同じであれば常に同じ値になるという属性(参照透明性)を依然として有している場合には、その関数を純粋であると言い、それのみによって成り立つ言語も純粋関数型言語と言う。HaskellやCleanの純粋性は、この意味である。
従って、定義としては、@関数が副作用を持たないこと、A関数に副作用はあるが、参照透明であることの2つの意味があると考えてよい。
→Abstraction、 Referential Transparency
[←先頭へ]| R |
Referential Transparency
参照透明性とか、参照透過性などと訳されており、関数型言語の主要な特性の1つとされている。
ありていに言えば、式・関数の結果値は実引数が同じであれば必ず1つに定まることをいう。つまり、実引数が何であるかにのみ依存していて、評価順序がなんであるか等という事には依存していない。
計算モデルのレベルでは、Church-Rosser propertyやdiamond property、合流性(confluency)等がこれに相当する。
命令型言語では、関数の結果値は実引数が同じであっても1つに定まるわけではない。これは主として、命令型が副作用(side effect)を持つからである(勿論全ての原因を副作用に求めることができるわけではない)。
例えば、以下のコードを考える。
#include <stdio.h>
int flag = 0;
int func(int n)
{
if(!flag) flag++;
else flag=0;
if(flag) n--;
else n++;
return n;
}
int main(void)
{
printf("First : %d\n", func(0)); /* 注1 */
printf("Second : %d\n", func(0)); /* 注2 */
return 0;
}
このような場合、注1でのfunc(0)と注2でのfunc(0)はその結果が違う。これは、この関数が実引数にのみ依存しているのではなく、その時々の文脈(ここでは、グローバル変数flag)によって結果値が変ってしまうからである。つまり、このfuncは、参照透明ではない。このようなことは純粋な関数型言語の式・関数にはないとされている。→Correctness、 Free variable 、 Abstraction、 Purity、 Definitional mechanism、 Strictness analysis、 Imperative paradigm、 Functional paradigm、 Destructive update(assignment)、 Side effect、 deforestation
[←先頭へ]| S |
Scope
有効範囲とか通用範囲などと訳されている。単にスコープともいう。論理学では、作用域と訳される。
オブジェクトの名前が可視的である或は意味をなしているプログラムコードの部分のことである。つまり、その有効な範囲内においてのみ、オブジェクトの名前は、そのバインドされている実体にアクセスできる。ある名前によってそれにバインドされている実体を参照できる範囲といってもよい。
これだけでは何とも意味がわからないと思うので、例を挙げる。
-------------------------(1)
int x; // A
-------------------------(2)
int main(...)
{
int x; // B
-------------------(3)
for(...)
{
int x; // C
}
-------------------(/3)
return 0;
}
-------------------------(/2)
-------------------------(4)
function(...)
{
int x; // D
}
-------------------------(/4)
-------------------------(/1)
C言語等を例にしたが、ここでは、A、B、C、Dのスコープは各々、(1)、(2)、(3)、(4)である。Cのxは、(3)の範囲内においてのみ、意味をなす、つまり、xという名前がCの宣言でバインドされた実体を指すことを意味する。→Bound variable、 Block、 Static binding、 Dynamic binding、 Namespace
[←先頭へ]SICP
「計算機プログラムの構造と解釈(Structure and Interpretation of Computer Programs)」(by Abelson & Sussman)という本の略称。ビアソンから翻訳が出ている。
表紙に魔術師が描かれていることから、ウィザード本などとも言われている。
Schemeを使って、計算機科学の基礎の勉強についてMIT等で使用されている本である。
ただ、基礎とはいうものの、関数プログラミングにとって基礎ともいい得るような本である。非常に優れているだけでなく、一読の価値があるものである。
但し和訳はいささか読みにくいのでそこは注意。まあ、人のことはいえないかもしれないが。原書は、Web上にもアップされている。
ここを参照。
→Assignment、 B&W、 Imperative paradigm
[←先頭へ]Side effect
副作用と訳されている。一般の場合と同じである。
定義としては、ある演算が実行される場合の結果値以外の全ての効果ということになるが、もうちょっと画定すると、実行環境(environment)の状態(state)を変更/修正する言語の構成要素又は効果それ自体ということになるかと思う。図式的に見てもらった方が分かると思うので、抽象(abstraction)を参照。
具体例としては、グローバル変数の例を参照透明性(referencial transparency)の所で載せ、別名の問題をdefinitional mechanismのところで載せたので(各項を参照)、ここでは、別の例を2つ取り上げる。
まず、以下のような例である。
int a=1;
int b=-1;
...
void swap(int& a, int& b)
{
int tmp;
tmp=a;
a=b;
b=tmp;
}
...
swap(a,b); // (a)
cout << "a : " << a << ' ' << "b : " << b << endl; // (1)
swap(a,b); // (b)
cout << "a : " << a << ' ' << "b : " << b << endl; // (2)
この場合、aとbは、swap内で変更されている。この為、(1)と(2)では結果が違う。つまり、swapの結果は、その時々の環境(ここでは、aとb)の状態に大きく依存し、しかも、次の環境の状態にも大きく影響を与える。従って、その実行順序が大きな意味を持っているということになる。つまり、意図する結果を実現したいならば、(a)と(b)の順序に気を使っていなければならない。しかし、これは望んだ副作用(desired side-effect)だと言えるので、これを問題だと思うことはないだろう。むしろ、このような副作用がなければこの手続はおそらく意図する目的を達成できない。ただ、これはもはやブラックボックスとしての関数・手続を維持していない。ルーチン又は部分プログラムというべきだろう。
/* サンプルA */
int main(argc int,char *argv[])
{
FILE *fin = fopen(argv[1],"r");
if(fgetc(fin)=='M') puts("男だよ"); /* (1) */
else if(fgetc(fin)=='F') puts("女だよ"); /* (2) */
else puts("?");
fclose(fin);
return 0;
}
この例は、おそらく意図したいことを実現するプログラムとはなっていない。(1)のfgetcと(2)のfgetcは出力するオブジェクトが違うからである。つまり、(1)と(2)は当該ファイルの同じ場所の文字を取り出してきてはいない。それは、fgetc関数は、その結果値の出力とは別に、fgetcの外のオブジェクトたるfinのファイル位置指定子を黙示的に変更してしまっている(副作用)からである。つまり、その時々の環境(ここでは、fin)の状態に依存し、そして影響を与えている。従って、以下のようにその副作用を踏まえてプログラムをしなければならない。
/* サンプルB */
int main(argc int,char *argv[])
{
FILE *fin = fopen(argv[1],"r");
int c;
if((c=fgetc(fin))=='M') puts("male"); /* (1) */
else if(c=='F') puts("female"); /* (2) */
else puts("?");
fclose(fin);
return 0;
}
副作用を考慮する為、以上のように新たに変数cを設けてfgetcを2回呼出すことなく対処するということになる。→Abstraction、 Environment、 Definitional mechanism、 Destructive update(assignment)、 Referential Transparency、 Correctness
[←先頭へ]Static binding
静的束縛とか訳されている。或は静的バインディングなどともいわれる。同一の意味を持つ言葉に、static scoping(scope)がある。lexical scoping(binding)等ともいわれる。
定義としては、ある名前がどのオブジェクトにバインドされているのかがプログラムテキストから直接分かるような束縛をいう。テキストから直接分かるというのは、人間から見て分かるというのではなく、字句解析によって直接分かるという意味である。
静的束縛は、このために、束縛を実行時ではなくコンパイル時に行うことができる。
具体例については、動的束縛(dynamic binding)の項を参照。
Strictness
正格性とか狭義性とか訳されている。厳密性とも訳されたりしている。或は単にストリクト性などとも言われる。これの反対は、非正格性(non-strictness)である。
定義としては記号的表現で定式化できるのだが、ここではそれはしない。そして、これは、関数又はその引数の属性であり、関数引数の評価によっては当該プログラムの停止性が変更されないという性質を言う。より簡単に言うと、関数の実引数がその関数の評価前に評価されるという性質をいう。別の言い方をすると、関数評価の際には引数の値が常に必要とされるという性質である。
計算モデルのレベルでは、applicative orderやinnermost reduction等に相当する。
また、評価順序(evaluation order)の観点からは、先行評価(eager evaluation)に相当する。
正格性に関してあげるべき特徴は主として3つある。つまり、非正格性に比べて効率的な実装を達成できること、ある関数がある引数に関して正格であると関数の停止性(termination)を充足しない場合が多いということ、及び、関数の結果に関係ない不必要な引数を評価する場合もあるということである。これらの関する詳しい説明は、evaluation orderの項を参照。
なお、引数を完全に先行評価する場合を、ハイパー正格(hyperstrict)などと言う。完全にというのは、渡される引数が合成データ構造等である場合に、その個々の要素も関数本体評価前に評価するという意味である。関数適用前にその属性を持つ引数は常に正規形であるという意味であると言ってもいいかもしれない。
→Termination、 Strictness analysis、 Evaluation order
[←先頭へ]Strictness analysis
正格性検査、ストリクト性解析などと訳されている。個人的には、正格性分析の方が正確だとは思うが、それはどうでもよい。
定義としては至って簡単で、関数の引数の正格性を検査することをいう。正格性(strictness)に関しては、その項を参照。もうちょっと詳しく言うと、関数を評価する際に、その引数の値が必要かどうかを調べることをいう。これは、実行時にではなく、コンパイル時に行われる。従って、正格性情報を取得する為のコンパイル時検査ということができるだろう。
正格性分析は、抽象解釈(abstract interpretation)と呼ばれる技術を使用して、ラムダ計算の中で説明されることが多い。抽象解釈は、その項を参照。ただ、Cleanの場合は、計算モデルがラムダ計算ではなくて、グラフ書換えシステムなので、それに伴って抽象簡約(abstract reduction)と呼ばれている。
このような分析が有用である理由は、効率性の為である。つまり、純粋関数型言語では、参照透明性(referencial transparency)が確保されている為、様々な評価順序(evaluation order)の選択肢を取り得るのだが、停止性(termination)の観点から、遅延評価(lazy evaluation)を基本的に採用している。しかし、遅延評価は、ほとんどの場合において先取評価(eager evaluation)よりも効率が悪い。そこで、正格であっても停止性を満足する関数引数に付いては、先取評価に変換した方が効率的であるということで、正格でもよい引数を検査することにしているのである。以上に出てきた各概念についてはその項を参照。
→Strictness、 Referential Transparency、 Termination、 Abstract interpretation
[←先頭へ]Substitution
数学上の代入に相当するものである。代入とも訳されているのが、計算機科学の世界では、代入にはassignmentをあてているので、置換とも訳される。
定義としては、数学の代入を知っている人ならばすぐに分かると思うが、ある式内においてある変数をある値で置き換えることである。
これは、assignment(これも代入と訳されている)とは異なる。assignmentは、正確には割当とでも言うべきもので、同一プログラム又は関数内で記憶状態たるオブジェクトの値を他のものに変更することを言う。assignmentについては、その項を参照。substitutionには、記憶状態の破壊的変更という要素はない。変数の名前とある値を束縛するに過ぎない。
| T |
Termination
停止性とか訳されている。
別に難しい概念ではない。プログラムがその意図通りに動く為には、そのプログラムが必ず(正常に)終了しなければならないが、その必ず停止する性質を停止性という。これについては特に例を挙げない。すぐに分かることだと思う。
関数型言語との関連では、遅延評価(lazy evaluation)と全的正当性(total correctness)との関係が重要である。詳しくは、評価順序(evaluation order)と正当性(correctness)の項を参照。
→Correctness、 Strictness、 Strictness analysis、 Evaluation order
[←先頭へ]Type checking
型検査と訳されている。あるいは型チェックなどとも言う。
定義としては、ある演算(関数、手続等)が適切な型を持つデータ構造に適用されているかを検査することをいう。
型検査の意義については、説明しない。それは型の意義の中に含まれているからである。
型検査は、いくつかの観点からいくつかの区別がなされている。
まず、検査時期に関して、コンパイル時に行うことを静的型検査(static type checking)といい、実行時に行うことを動的型検査(dynamic type checking)という。これは、型付け(typing)に関連してくるので、この区別に関してはそちらの項を参照。
次に、型エラーに関して、合格すれば型エラーがないという検査を強い型検査(strong type checking)といい、合格しても型エラーから自由であるとは限らない検査を弱い型検査(weak type checking)という。これも型付けに関連してくるので、そちらの項を参照。
第三に、検査方法に関連して、2つの区別がある。まず、同一名の型を持つ場合に同じ型だとされる検査をName Equivalenceといい、同一のデータ構造を持つ場合に同じ型だとされる検査をStructural Equivalenceという。これら2つを合わせてType Equivalenceという。
例を挙げる。
typedef int YetAnother
int a;
YetAnother b;
...
a=b; /* (1) */
この場合のaとbはname equivalenceでは同じ型ではないということになる。型名が違うからである。従って、この代入(1)は無効である。ところが、structural equivalenceでは同じ型になるので、この(1)は有効である。→Typing、 Type inference、 Dynamic binding
[←先頭へ]Type inference
型推論と訳されている。
これは命令型では比較的馴染みの無い概念であると思う。それはMLを中心として発展してきたという歴史的な理由が大きい。ただ、別に関数型に限られるわけではなく、命令型の言語に限っても知っておくと有意義であると思う。
定義としては、明示的な型宣言が無い場合にオブジェクトの型を意味の通るように、プロセッサが、コード上の手かがりを頼りに推論して決定することを言う。例えば、以下に例を挙げる。
? fac(? n)
{
if(n==0) return 1;
else return n * fac(n-1);
}
これはナイーブな階乗関数であるが、?の部分が分からないものとする。しかし、?の型は推論できる。というのも、n==0の場合には1を返し、nは0と比較されているからである(つまり、引数・結果ともに整数である)。従って、こういう場合には明示的に型宣言をしなくとも型推論機構があればコーディングの手間が省ける。こういうように、推論することで型を特定できる場合を単相型推論(monomorphic type inference)という。
? identity(? n)
{
return n;
}
こういう場合引数・結果共にどんな型でもあり得る。従って、こういう場合は単相的には推論できない。ただ、多相的には推論できる。つまり、これは、多相関数(polymorphic function)である。多相性(polymorphism)については説明しない。オブジェクト指向に関する情報源は多数あるのでそちらを参照して欲しい。そして一般に、このように推論されることを、多相型推論(polymorphic type inference)という。Typing
型付きなどと訳されている。typingという形ではなく、typedというようにも使用される。
定義としては、型という概念をもっている言語の属性である。特に難しいことではないと思う。もう少し厳密に言うと、プログラム中に現れる式が取ることのできる値に制限(つまり、型)のある言語の性質をいう。
そして、型のある言語を一般に、型付き言語(typed language)といい、型のない言語を型無し言語(untyped language)という。
その型付き言語は一般に、2つの基準から4つの種類に分けることができる。
まず、強い型付け(strong typing)と弱い型付け(weak typing)である。つまり、強く型付けされているとは、言語プロセッサの型検査に合格したプログラムは、型エラーを持たないことが保証されていることを言う。逆に、弱く型付けされているとは、それが保証されているわけではないことをいう。型検査(type checking)については、その項を参照。
次に、静的な型付け(static typing)と動的な型付け(dynamic typing)である。静的な型付けとは、あらゆる式等がプログラマの指定した型を持ち、その型のまま固定され、他に変更されることのない性質をいう。従って、型検査をコンパイル時に行うことができる(静的型検査)。静的に型付けされた言語は、全ての式をコンパイル時に型検査できるので、全て強く型付けされた言語であるともいわれている。
これに対して、動的な型付けとは、全ての式等がプログラマの指定した型を持つわけではなく、異なった場所では異なった型を持ち得る性質をいう。値だけが固定した型を持つといってもよい。従って、型検査は実行時に行われることになる(動的型検査)。
具体例を挙げる。
int even(int n)
{
return (n % 2) == 0;
}
これは見ての通り静的な型付けの言語の関数である。つまり、nがどんな値かは分からないものの、それが整数であり、従って、%演算子のオペランドは両方整数であり、従って、n % 2も整数であり、そして、==演算子が整数のオペランドをもつことも分かり、結局返り値がintであることも分かる。
even(n)
{
return (n % 2) == 0;
}
こうすると、nの型が分からないので、n % 2も==も返り値も全ての型が分からないということになる。これは、呼出如何によってはいかにもエラーが出そうだが、逆に、even(4)、even(6.0)、even('c')等によって呼び出すことができる。→Type checking、 Dynamic binding
[←先頭へ]| V |
Variable
変数と訳されることがほとんどである。プログラミング言語においてはまさに中核概念である。
定義としては、命令型プログラミングと宣言プログラミングの世界では異なる。つまり、命令型言語における変数とは、値を持っているオブジェクトのことである。値(と名前(name))を持っている箱(正確にはメモリ領域)と言っていいかもしれない。従って、その変数には値を変える操作、つまり代入(assignment)が可能である。一方で、宣言プログラミング、なかんずく関数型言語における変数とは、数学上の変数と同じく、未知数である。つまり、未だに分かっていない未知の値のこととなる。この場合、代入はsubstitutionであり、1つの通用範囲内では、1つの変数に1回しかない。従って、値を変える操作というものは観念し得ない。各代入に付いては、各項を参照。
変数は、色々な観点から数種に分類できるが、それはとりあえず省略する。その中のものとしては、自由変数(free variable)と束縛変数(bound variable)もあり、既述なので、各項を参照。
また、宣言方法、束縛方法等も色々に渡り、この用語集全体に渡るので、全体を参照した方が良いかもしれない。
→Assignment、 Bound variable、 Free variable 、 Name、 Substitution
[←先頭へ]