なぜ関数プログラミングは重要か

Institutionen för Datavetenskap,

Chalmers Tekniska Högskola,

41296 Göteborg, SWEDEN.

rjmh@cs.chalmers.se

この論文は1984年以来何年ものあいだChalmers大学のメモとして回覧された。 1989年と1990年に幾分か改訂をしたのが[Hug89]と [Hug90]である。この版はもとのChalmer大学のメモ のnroff原稿をもとにしてLaTeX用にいくぶん編集を加え、出版用のものに近づけ たものである。さらに、一つ二つの誤りを訂正している。いくぶん古臭い整形で あること、例が Haskell ではないことをお許しいただきたい。

概要

ソフトウェアがどんどん複雑になるにつれ、それを上手く構造化することがま すます重要になっている。上手く構造化したソフトウェアは書きやすく、デバッ グしやすく、かつ、将来のプログラミングコストを引き下げるために再利用可 能な部品群を提供するものである。従来の言語では問題を部分化する方法につ いて概念的な限界がいくつかある。関数型言語はこれらの限界を押し広げるも のである。この論文では、とくに関数型言語の二つの特徴、モジュール化に大 きく貢献する高階関数と遅延評価を披露する。例のように、リストとツリーを 操作し、いくつかの数値計算アルゴリズムをプログラムし、また、α-β発見 法(ゲームをするプログラム中で用いられる人工知能由来のアルゴリズムのひと つ)の実装をおこなう。モジュール性はプログラミング成功の鍵であるから、関 数型言語は実世界にとって極めて重要である。

1. イントロダクション

本論文は「実世界」にとって関数プログラミングが極めて重要であることを例 示し、関数プログラマが、関数プログラミングの利点が何であるかを明確にす ることでその利点をフルに活用できるよう支援するひとつの試みである。

関数プログラミングは、関数だけで全体が構成されているプログラムの故にそ う呼ばれている。メインプログラムそれ自身は、プログラムへの入力を引数と して受けとり、その結果としてプログラムの出力を供給する関数として書く。 典型的にはこのメイン関数は別の関数を使って定義する。それらもまた、さら に多くの関数を使って定義し、最終的なレベルでは関数は言語のプリミティブ となる。これらの関数は通常の数学的な関数に似ており、この論文では普通の 等式で定義する。記法はTurnerの言語、Miranda(TM) [Tur85]に準じたものであるが、関数型言語の知識 なしでも読めるものになっているはずである。(MirandaはResearch Software Ltd. の商標である。)

関数プログラミングの特徴や利点は多かれ少かれ以下のように要約されること がよくある。関数プログラムは代入文を含まない。それゆえ、変数は一度 値を与えられたら変更されない。もっと一般的ないいかたをすれば、関数プロ グラムには全く副作用がない。関数の呼出しは、結果を計算する以外の作用は もたない。このことは、バグの大きな源のひとつを断つ。また、実行順を気に しなくてよい。式の値を変更する副作用がないので、いつの時点で式の値を評 価してもよい。プログラマはフローの制御を指示するという負担から解放され る。式をいつの時点で評価してもよいので、変数とその値と自由に交換するこ とができる。すなわち、プログラムは「参照透明」である。この自由のおかげ で、関数プログラムはそうではない従来のプログラムより、数学的な扱いが容 易である。

このような「利点」のカタログは確かにそのとおりであるが、かやの外の者が 大したことではないと考えても驚いてはならない。つまり、関数プログラミン グは「〜ではない」ということ(代入はない、副作用はない、制御フローはない) を多く語っているが、「〜である」ということはそれほど語っていない。関数 プログラマはどちらかというと人生の楽しみを否定し、それにより高潔な者に なることを望む中世の修道僧のように思える。もっと物質的な利益に興味のあ る者にとってはこれらの「利点」はあまりピンとくるものではない。

関数プログラマは大きな物質的利益があると主張する。関数プログラマは従来 のプログラマより桁違いに生産的である。関数プログラムは桁違いに短かいか ら。しかし、なぜそうなるか。これらの「利点」にもとづいて示唆できる、微 かにもっともらしい理由は、従来のプログラムの90%は代入文であり、関 数プログラムは代入文を削除できるというものである。これは全くもって滑稽 である。もし、代入文の削除がそれほど大きな利益をもたらすというなら、 FORTRANプログラマは20年ものあいだそうしたであろう。ひとつの言語をその言 語の特徴のひとつを削除することでより強力にするなどということは論理的に 不可能である。その特徴がたとえとても良くないものであってもである。

関数プログラマでさえ、いわゆる利点には満足してはいないはずである。それ は、関数型言語を活用するのに役にたたないからである。代入がないだけ、あ るいは参照透明であるだけではプログラムを書くことはできない。そこには、 プログラムの品質をはかる尺度がない故に、めざすべき理想がないのである。

あきらかに、このように関数プログラミングを特徴付けることは不適当である。 代わりのなにかを見つけなければならない。関数プログラミングの力を説明す るだけではなく、関数プログラマが懸命にめざすところを明示するなにかを。

2. 構造化プログラミングとの類似

関数プログラミングと構造化プログラミングの類似を描くことは有用である。 過去、構造化プログラミングの特徴や利点は多かれ少かれ以下のように要約さ れてきた。構造化プログラムはgoto文を含まない。構造化プログラム ではブロックが複数の入口や複数の出口をもたない。構造化プログラムは非構 造化プログラムより数学的な扱いが容易である。構造化プログラミングのこれ らの「利点」は、気持の上では、先に議論した、関数プログラミングの「利点」 に似ている。これらは、本質的には否定形の謂であり、「本質的なgoto」 などに関する実りのない主張を導いた。

後になって改めて考えると、構造化プログラムのこうした特徴は、もちろん有 用であるが、事の核心に至っていないことは明かである。構造化プログラムと 非構造化プログラムのあいだの最も重要は相違は構造化プログラムはモジュー ル化という方法で設計されるということである。モジュール化設計は大きな生 産性向上をもたらす。まず第一に小さなモジュールは素速くかつ早期にコード 化できる。第二に汎用のモジュールは再利用可能で、これが後のプログラムを 速く開発することを可能にする。第三にプログラムのモジュールは独立に試験 することが可能で、これがデバッグ時間の節減に役立つ。

上のことと、gotoがないことなどは殆ど関係がない。これは小規模プ ログラミングに役立つが、一方で、モジュール化設計は大規模プログラミング に役立つ。それゆえ、多少やることが増えるが、FORTRANあるいはアセンブリ言 語において構造化プログラミングの利点を亨受することができる。

今やモジュール化設計がプログラミングを成功させる鍵であることは一般に受 け入れられている。そして Modula-II [Wir82]、 Ada [oD80]、 Standard ML [MTH90]のような言語はとくにモジュー ル性を向上させるように設計された機能を含んでいる。しかしながら、多くの 場合、見過ごされる非常に重要なポイントがある。問題を解くための部品プロ グラムを書くとき、その問題を部分問題に分割し、部分問題を解き、その解を 合成する。元の問題を分割する方法は、部分解を貼り合せる方法に直接依存す る。それゆえに、概念的には問題をモジュール化する能力を高めるためにはそ のプログラミング言語のなかで新たな糊の類を用意しなければならない。複雑 なスコープ規則や分割コンパイルの規約は事務的な詳細でしかなく、問題を分 解する新しい概念的道具をあたえるものではない。

糊の重要性は、大工仕事との類比によって、正しく評価できる。椅子は、部分 (座部、脚、背もたれなど)を作り、それらを正しくくっつけ合せることで容易 に作ることができる。しかし、これはジョイントと木を張り合せるという能力 に依存する。その能力がなければ、椅子を作る方法はひとつ木の塊からそれを 彫り出す以外なく、非常に難しい作業になる。この例は、モジュール化の強大 な力と正しい糊を持つことの重要性の両方を例示するものである。

さて、関数プログラミングに戻ろう。この論文の残りの部分では関数型言語が 二つの新しい、非常に重要な糊を提供するのだという議論をしよう。あたらし い方法でモジュール化可能でそれによって非常に簡潔になるプログラムの例を いくつもあげていこう。これこそが関数プログラミングの力の鍵であり、モジュー ル化を大幅に向上することが可能になる。また、関数プログラマが目指して邁 進すべき目標は、これから述べる新しい糊で、より小く、より簡潔に、より汎 用的なモジュールを貼り合せるということである。

3. 関数の貼り合せ

二種類の糊のうち最初のものは単純な関数を貼り合せてより複雑な関数にする ものである。これは簡単なリスト処理問題(リストの要素の足しあげ)で説明す ることができる。リストを

listof X ::= nil | cons X (listof X)

[] の意味は nil [1] の意味は cons 1 nil [1,2,3] の意味は cons 1 (cons 2 (cons 3 nil))

sum nil = 0

sum (cons num list) = num + sum list

+---+ sum nil = | 0 | +---+ +---+ sum (cons num list) = num | + | sum list +---+

sum = reduce add 0

add x y = x + y

(reduce f x) nil = x (reduce f x) (cons a l) = f a ((reduce f x) l)

このようにして、sumをモジュール化したのでこの部品を再利用する利益を得る ことができる。最も興味のあるのはreduceの部分である。これは、余分にプロ グラミングをすることなく、リストの要素を掛け合せる関数を書くのに使える。

product = reduce multiply 1

anytrue = reduce or false

alltrue = reduce and true

cons 1 (cons 2 (cons 3 nil))

add 1 (add 2 (add 3 0)) = 6

multiply 1 (multiply 2 (multiply 3 1)) = 6

append a b = reduce cons b a

append [1,2] [3,4] = reduce cons [3,4] [1,2] = (reduce cons [3,4]) (cons 1 (cons 2 nil)) = cons 1 (cons 2 [3,4])) (replacing cons by cons and nil by [3,4]) = [1,2,3,4]

doubleall = reduce doubleandcons nil where doubleandcons num list = cons (2*num) list

doubleandcons = fandcons double where double n = 2*n fandcons f el list = cons (f el) list

fandcons f = cons . f

(f . g) h = f (g h)

fandcons f el = (cons . f) el = cons (f el) したがって、 fandcons f el list = cons (f el) list

doubleall = reduce (cons . double) nil

doubleall = map double map f = reduce (cons . f) nil

summatrix = sum . map sum

これだけの例があれば、ちょっとしたモジュール化が大いに役立つ可能性がある ことを納得するはずだ。単純な関数(sum)を「高階関数」といくつかの単純な関 数の合成としてモジュール化することにより、とくに余分にプログラミング努 力をすることなくリスト上の多くの関数を書きおろすのに使える、(reduce)と いう部品に到達した。リスト上の関数にとどまることはない。もうひとつの例 として、つぎに定義される、ラベル付順序ツリー

treeof X ::= node X (listof (treeof X))

1 o ／ ＼ ／ ＼ ／ ＼ 2 o o 3 | | | o 4

node 1 (cons (node 2 nil) (cons (node 3 (cons (node 4 nil) nil)) nil))

redtree f g a (node label subtrees) = f label (redtree' f g a subtrees) redtree' f g a (cons subtree rest) = g (redtree f g a subtree) (redtree' f g a rest) redtree' f g a nil = a

sumtree = redtree add add 0

add 1 (add (add 2 0) (add (add 3 (add (add 4 0) 0)) 0)) = 10

labels = redtree cons append nil

cons 1 (append (cons 2 nil) (append (cons 3 (append (cons 4 nil) nil)) nil)) = [1,2,3,4]

maptree f = redtree (node . f) cons nil

4. プログラムの貼り合せ

関数型言語が用意するもうひとつの新しい種類の糊はプログラム全体を貼り合 せることを可能にする。完成した関数プログラムは入力から出力への一つの関 数にすぎないことを思いおこせ。もし、fとgがそうしたプログラムであるなら、 (g . f)は一つのプログラムであり、入力(input)に適用すると

g (f input)

この評価方式はfをできるだけ走らせないようにするので、「怠惰(遅延)評価」 と呼ばれている。これにより、ひとつのプログラムを多くの可能な答を構成 する生成器と適切なものを選ぶ選択器にモジュール化することが現実的なもの となる。いくつかのシステムではプログラムをこのような方法で同時に走らせ ることが可能であるが、関数型言語だけが、すべての関数について一律に遅延 評価を用い、プログラムの任意の部分がこの方法でモジュール化可能である。 遅延評価はおそらく関数プログラマのレパートリの中で最も強力なモジュール 化の道具である。

4.1. ニュートン-ラプソン法による平方根

いくつかの数値計算アルゴリズムのプログラミングをすることで遅延評価の力 を説明しよう。先ず最初に、平方根を求めるニュートン-ラプソンのアルゴリ ズムを考えよう。このアルゴリズムは数値Nの平方根を、初期近似値a0から始 めて、以下のルールを使って、どんどん改良していって、求める。

a(n+1) = (a(n) + N/a(n)) / 2

a = (a + N/a) / 2 したがって、 2a = a + N/a a = N/a a*a = N a = squareroot(N)

C N IS CALLED ZN HERE SO THAT IT HAS THE RIGHT TYPE X = A0 Y = A0 + 2.*EPS C THE VALUE OF Y DOES NOT MATTER SO LONG AS ABS(X-Y).GT.EPS 100 IF (ABS(X-Y).LE.EPS) GOTO 200 Y = X X = (X + ZN/X) / 2. GOTO 100 200 CONTINUE C THE SQUARE ROOT OF ZN IS NOW IN X

ニュートン-ラプソンのアルゴリズムは近似値の列を計算するので、プロ グラム中ではこれを、近似値のリストとして表現するのが自然である。各近似 値はその手前の近似値から次の関数によって導びく。

next N x = (x + N/x) / 2

[a0, f a0, f(f a0), f(f(f a0)), ..]

repeat f a = cons a (repeat f (f a))

repeat (next N) a0

平方根発見器の残りの部分は、関数withinで、この関数は許容誤差と近似値 のリストを引数としてとり、そのリストを見て与えられた許容誤差よりも差の 小さい二つの連続する近似値を探す。これは次のように定義できる。

within eps (cons a (cons b rest)) = b, if abs(a-b) <= eps = within eps (cons b rest), otherwise

sqrt a0 eps N = within eps (repeat (next N) a0)

relative eps (cons a (cons b rest)) = b, if abs(a-b) <= eps*abs b = relative eps (cons b rest), otherwise

relativesqrt a0 eps N = relative eps (repeat (next N) a0)

4.2. 数値微分

easydiff f x h = (f(x+h)-f x) / h

differentiate h0 f x = map (easydiff f x) (repeat halve h0) halve x = x/2

within eps (differentiate h0 f x)

正しい値 + h に関連する誤差項

a(i) = A + B*(2**n)*(h**n) および、 a(i+1) = A + B*(h**n)

a(i+1)*(2**n) - a(i) A = -------------------- 2**n - 1

elimerror n (cons a (cons b rest)) = = cons ((b*(2**n)-a)/(2**n-1)) (elimerror n (cons b rest))

order (cons a (cons b (cons c rest))) = = round(log2( (a-c)/(b-c) - 1 )) round x = x rounded to the nearest integer log2 x = the logarithm of x to the base 2

improve s = elimerror (order s) s

within eps (improve (differentiate h0 f x))

within eps (improve (improve (improve (differentiate h0 f x))))

super s = map second (repeat improve s) second (cons a (cons b rest)) = b

within eps (super (differentiate h0 f x))

4.3. 数値積分

easyintegrate f a b = (f a + f b)*(b-a)/2

integrate f a b = cons (easyintegrate f a b) (map addpair (zip (integrate f a mid) (integrate f mid b))) where mid = (a+b)/2

zip (cons a s) (cons b t) = cons (pair a b) (zip s t)

integrate f a b = integ f a b (f a) (f b) integ f a b fa fb = cons ((fa+fb)*(b-a)/2) (map addpair (zip (integ f a m fa fm) (integ f m b fm fb))) where m = (a+b)/2 fm = f m

within eps (integrate f a b) relative eps (integrate f a b)

super (integrate sin 0 4) improve (integrate f 0 1) where f x = 1/(1+x*x)

5. 人工知能からの例

関数型言語が本来、強力なもので、それは、高階関数と遅延評価というふたつ の新しい種類の糊をもつためであると主張した。この節ではより規模の大きい 例を人工知能からとり、この二つの糊をつかってどのように非常に単純 にプログラムすることが可能かを示す。

選択した例はα-β発見法というゲームプレイヤの状態がどのくらい良いものか を見積るアルゴリズムである。このアルゴリズムはゲームがどのように展開す るかを先読みすることで動作するが、役にたたない道筋を追及することは回避 する。

ゲーム状態がpositionという型のオブジェクトで表現されているものとしよう。 この型はゲーム毎に変化するものになる。これについては何も仮定しない。あ る状態からどのような動きが可能かを知る方法がなければならない。次のよう な関数があると仮定する。

moves: position -> listof position

| | X| | |X| | | -+-+- -+-+- -+-+- -+-+- moves | | = [ | | , | | , |X| ] -+-+- -+-+- -+-+- -+-+- | | | | | | | | | | O| | |O| -+-+- -+-+- -+-+- moves |X| = [ |X| , |X| ] -+-+- -+-+- -+-+- | | | | | |

関数movesがあれば、最初のステップはゲームツリーを構築することである。こ のツリーはノードに状態のラベルが付いており、ノードの子はそのノードから 一手で到達可能な状態でラベル付けされたノードとなる。すなわち、もしノー ドが状態pのラベルがついたものであれば、その子は(moves p)の状態のどれか でラベル付けされている。ゲームツリーは無限に十分なり得る。もし、ゲーム が勝負がつかずに無限につづく場合である。ゲームツリーは第2節で議論したツ リーと同じものである。おのおののノードはラベル(状態を表現している)とサ ブノードのリストとを持つ。それゆえに、これらを表現するのに同じデータ型 を使うことができる。

ゲームツリーはmovesを繰り返し適用することによって、構築する。ルートの状 態からはじめて、movesはそのルートのサブツリーのラベルを生成するのに用い る。その後movesはサブツリーのサブツリーを生成するのに用いる。再帰のこの パターンは以下の高階関数を用いて表現できる。

reptree f a = node a (map (reptree f) (f a))

gametree p = reptree moves p

| | -+-+- gametree | | -+-+- | | | | -+-+- = | | -+-+- | | ／ | ＼ ／ | ＼ ／ | ＼ ／ | ＼ ／ | ＼ ／ | ＼ X| | |X| | | -+-+- -+-+- -+-+- | | | | |X| -+-+- -+-+- -+-+- | | | | | | ／|＼ ／|＼ ／ ＼ ... ... ／ ＼ ／ ＼ ／ ＼ O| | |O| -+-+- -+-+- |X| |X| -+-+- -+-+- | | | | ／|＼ ／|＼ ... ...

static: position -> number

このような静的評価の木が与えられた場合、そのなかの状態の正確な値は何で あろうか。特にルートの状態に帰すべき値は何であろうか。その静的な値ではない、 なぜなら、その静的な値はおおまかな推測にすぎないからだ。あるノードに帰 すべき値はそのサブノードの正確な値から決定されるものでなければならない。 この決定は各プレイヤが可能な手のうち最良のものを選ぶという仮定を用いて 行う。高い値ほどコンピュータにとって良い状態であるということを思いおこ せば、任意の状態からのコンピュータの手は正確な値が最大のサブノードに誘 導する手を選択することになろう。同様にして、対戦者は正確な値が最小のサ ブノードに誘導する手を選択することになる。コンピュータと対戦者は交互に 手を打つことを仮定すれば、ノードの正確な値は、コンピュータの番ならば、 関数maximiseが計算した値であり、対戦者の番であれば、minimiseが計算した 値である。

maximise (node n sub) = max (map minimise sub) minimise (node n sub) = min (map maximise sub)

maximise (node n nil) = n maximise (node n sub) = max (map minimise sub) minimise (node n nil) = n minimise (node n sub) = min (map maximise sub)

evaluate = maximise . maptree static . gametree

prune 0 (node a x) = node a nil prune n (node a x) = node a (map (prune (n-1)) x)

evaluate = maximise . maptree static . prune 5 . gametree

prune 5 . gametree

maptree static . prune 5 . gametree

これまでは、単純なミニマックス法についてのみ言及してきた。α-βアルゴリ ズムの核心は、多くの場合、ツリー全体を見ることなくmaxmiseあるいは minimiseの値を計算することができるという観察にある。以下のツリーを考え よ。

max ／ ＼ ／ ＼ ／ ＼ ／ ＼ min min ／ ＼ ／ ＼ ／ ＼ ／ ＼ 1 2 0 ?

maximise = max . maximise'

maximiseの定義からmaxを因子抽出することはやさしい。以下のようになる。

maximise' (node n nil) = cons n nil maximise' (node n l) = map minimise l = map (min . minimise') l = map min (map minimise' l) = mapmin (map minimise' l) where mapmin = map min

mapmin (cons nums rest) = cons (min nums) (omit (min nums) rest)

omit pot nil = nil omit pot (cons nums rest) = omit pot rest, if minleq nums pot = cons (min nums) (omit (min nums) rest), otherwise

minleq nil pot = false minleq (cons num rest) pot = true, if num<=pot = minleq rest pot, otherwise

evaluate = max . maximise' . maptree static . prune 8 . gametree

別の最適化をこの評価器にほどこすことができる。たとえば、今述べたα-βア ルゴリズムは、最良の手筋を最初に考えれば、最も良く働く。それは、もし、 非常に良い手を見付けたらそれより悪い手は、対戦者がそれに対して少くとも ひとつは良い応手をもつということを示す以外は必要のないものである。それ ゆえに、各ノードごとにサブツリーをソートし、コンピュータの手番のときは 大きい順に、そうでないときには小さい順にならべたいとおもう。これは以下 のような関数を書くことで実現できる。

highfirst (node n sub) = node n (sort higher (map lowfirst sub)) lowfirst (node n sub) = node n (sort (not.higher) (map highfirst sub)) higher (node n1 sub1) (node n2 sub2) = n1>n2

evaluate = max . maximise' . highfirst . maptree static . prune 8 . gametree

taketree n = redtree (nodett n) cons nil nodett n label sub = node label (take n sub)

もうひとつの改良は枝刈りの洗練である。上のプログラムは状態が動的なもの であるにもかかわらず、固定の深さの先読をする。たとえばチェスでクィーン 取りがかかっている状態なのにそれ以上先を読まないという判断をするかもし れない。いくつかの「動的」な状態を定義し、これらの状態のところで先読み が止ってしまわないようにするのが普通である。関数「dynamic」がこのような状態を識別できるものと仮定すると関数 pruneの定義に一行の等式を加えるだけでよい。

prune 0 (node pos sub) = node pos (map (prune 0) sub), if dynamic pos

6. 結論

この論文では、モジュール性がプログラミング成功の鍵であることを主張した。 生産性の向上を目的とした言語はモジュラプログラミングをよくよくサポート しなければならない。しかし、新しいスコープ規則や分割コンパイルの機能で は不十分である。モジュール性はモジュール以上の意味がある。問題を部分に 分解する能力は解を貼り合せる能力に直接に依存している。モジュラプログラ ミングを支援するには、言語は良い糊を用意しなければならない。関数型プロ グラミング言語は二つの新しいタイプの糊を供給する。すなわち、高階関数と 遅延評価である。これらの糊を用いて新しいわくわくするような方法でプログ ラムを簡単にモジュール化できる。その例をいくつも示した。より小さく、よ り汎用的なモジュールは、その後のプログラミングでより広く再利用すること ができる。このことは、なぜ関数プログラムが従来のものよりもずっと簡単に 小さく書けることの説明になる。そして、関数プログラマが狙うべき目標を用 意することになる。もしプログラムの部分のどこかがごちゃごちゃで複雑なも のであるなら、プログラマはそれをモジュール化し、その部品を汎用化しなけ ればならない。これを実現するためのツールとして高階関数と遅延評価が使え ることを期待するはずだ。

もちろん、高階関数や遅延評価の力や優雅を指摘したのはこれが最初ではない。 例えば、Turnerはこの二つが化学構造を生成するためのプログラムのなかで大 変有利に使えるのを示している[Tur81]。 AbelsonとSussmanはストリーム(遅延リスト)がプログラムの構造化に強力な道具 となることを強調した[AS86]。Hendersonはストリーム を関数型のオペレーティングシステムを構築するのに用いた[Hen82]。本論文の主に貢献するところはよりよいモ ジュール化がひとり関数型言語の力の鍵であると主張することである。

遅延評価をめぐる現在の論争にもかかわっている。関数型言語は遅延評価であ るべきだと信じるものもいれば、遅延評価であるべきではないと信じるものも いる。遅延リストを用意するだけで妥協するものもいる。この場合、(たとえば、 [AS86]のSCHEMEのように)特殊構文を用いてこれを構成 する。本論文では遅延評価が非常に重要で、第二級の市民権に格下げできない という更なる証拠を用意した。それは関数プログラマの持つおそらくもっとも 強力な糊であろう。このような極めて重要なツールへのアクセスを妨げてはな らない。

謝辞

この論文はOxfordのProgramming Research GroupでのPhil WadlerとRichard Birdとの多くの会話に負うところが大いにある。Charmers大学Göteborg校 のMagnus Bondessonは数値アルゴリズムのひとつの初めのころのバージョンに 深刻なバグがあることを指摘してくれた。そして、これに関して他の多くのも のの開発を促してくれた。この仕事はUK Science and Engineering Research Councilの支援により実現した。

[AS86] H. Abelson and G.J. Sussman. Structure and Interpretation of Computer Programs, MIT Press, Boston, 1986

[Hug89] J. Hughes. Why Functional Programming Matters, Computer Journal, 32(2), 1989

[Hug90] J. Hughes, Why Functional Programming Matters, In D. Turner, editor, Research Topics in Functional Programming, Addison Wesley, 1990

[MTH90] R. Milner and M. Tofte and R. Harper, The Definition of Standard ML, MIT Press, 1990

[oD80] United States Department of Defense, The Programming Language Ada Reference Manual, Springer-Verlag, 1980

[Hen82] P. Henderson, Purely Functional Operating Systems, 1982

[Tur81] D. A. Turner, The Semantic Elegance of Applicative Languages, Proceedings 1981 Conference on Functional Languages and Computer Architecture, Wentworth-by-the-Sea, Portsmouth, New Hampshire, 1981

[Tur85] D. A. Turner, Miranda: A non-strict language with polymorphic types, Proceedings 1985 Conference on Fucntional Programming Languages and Computer Architecture, pp.1-16, Nancy, France, 1985

[Wir82] N. Wirth, Programming in Modula-II, Springer-Verlag, 1982