だいぶ間が空いてしまったけど、久々に。
2. ISWIMと代数
Lucidは定義型言語(definitional language)である。
すなわち、Lucidプログラムの文(statement)はストリームとフィルタを定義する式であり、格納場所を更新するコマンドではない。
Lucidはこの意味で珍しい言語ではあるが、別に独特なものだというわけではない。
最初の定義型言語であるMcCarthyのLISPは、1960年、つまり約25年以上前に設計されたものである。
LISPプログラム(あるいは、少なくともLISPの「純粋な(=副作用を持たない)」サブセットのプログラム)は、値を求めたい式(expression)を伴った、副作用のない(side-effect-free)関数の定義の集合(これらは式で使われる)である。
純粋な「コア」言語には命令的な機能は含まれていないため、その意味論(semantics)は、式の値がその部分式の値にのみ依存するという意味で、参照透過(referentially transparent)となっている。
残念なことに、純粋なサブセットで書かれたプログラムは書くのが非常に面倒であり、また、容認できないほどに実行が非効率的だった。
後のバージョンの言語では、効率を改善するために命令的な機能(prog
形式やreplaca
、replacd
操作など)が追加された。
副作用が可能になり、それらはすぐに無慈悲にも濫用された。
それゆえ、参照透過性は失われ、言語の数学的な美しさは失われた。
これらの「汚れた」機能によって得られた時間と空間の大幅な節約は、純粋な適用型(applicative)、関数型(functional)言語は本質的に非効率的であり、命令的な機能はあらゆる言語に不可欠で、本質的に自然なものでもあるという信念を引き起こした。
純粋な関数型言語のかすかな希望が短期間で消滅したあと、別の定義型言語が現れるまでに数年が経過した。
この次の言語、LandinのISWIMは、彼の有名な「The Next 700 Programming Language」(Landin、1966)で導入された。
Landinの目的は、純粋で関数型な言語は、表記する上で煩雑さを必要としないことを示すことだった。
LISPの問題は、式がとても大きくなり、入れ子が深くなることがよくあったため(特にLISPでの括弧の使用)、非常に読みにくいことだった。
Landinは「where
節(where
clause)」を考案することでこの問題を解決した。
where
節は、式で使用される変数の「補助的な」定義の集合を伴った式である。
例えば、通常の式、
( (-b + sqrt(b*b - 4*a*c)) / (2*a) ) * ( (-b - sqrt(b*b - 4*a*c)) / (2*a) )
は、where
節を使って以下のように書き換えることが出来る:
r1 * r2 where r1 = (-b + sqrt(d)) / (2 * a); r2 = (-b - sqrt(d)) / (2 * a); d = b * b - 4 * a * c; end
d
はr1
とr2
を定義する式に現れ、その値はwhere
節の3番目の定義によって与えられることに注意すること。
(実際には、Landinは後述する理由で、whererec
という単語を使用していた。事実、以下で書かれているISWIMのwhere
節はすべて、Landinの規則に従えば、whererec
と書かれるべきものである)
Landinは上記の表記の他にいくつかの構文的バリエーションを提案していて、これは以下のように書くことも出来る:
let d = b * b - 4 * a * c in let r1 = (-b + sqrt(d))/(2 * a) and r2 = (-b - sqrt(d))/(2 * a) in r1 * r2 end end
ISWIMプログラムは単なる式であり、その出力は(式と見なされた)その値である。
(入力については後述)
ISWIMの正式な構文は抽象的なもので、仕様化されているのは解析木の集合であり、文字列の集合にはなっていない。
ファミリの特定のメンバの設計者は、具体的な(文字列の)構文でいくらかの自由が許されている。
この本では、慣習的によく用いられている具体的な構文を使用することにし、そこでは、馴染みのある二項演算子(+
など)を使ったよく知られた操作(足し算など)と、通常の括弧とカンマを使った関数適用が使われるものとする。
ISWIMを設計する際、明らかに、Landinは通常の数学のいくつかの一般的な表記法に正確に適合させるようにしている。
ISWIMでは、関数の定義を、それに続く節の中で等式を使って分かりやすく出来るようにしている:
dist(P, Q) where dist(A, B) = sqroot(sumsq(xcoord(A) - xcoord(B), ycoord(A) - ycoord(B))); sumsq(x, y) = x * x + y * y; end
where
節で定義されている式は、(上記のように)節で定義されている他の関数や変数で使用することが出来る。
関数を定義する式でも同じ関数を使うことが出来るので、以下のように関数fac
を再帰的に定義することも可能である:
fac(6) where fac(n) = if n < 2 then 1 else n * fac(n - 1) fi; end
以下のような、pow
、evenpow
、oddpow
の相互再帰的な定義も許されている:
pow(B, E) where evenpow(x, n) = pow(x * x, n / 2); oddpow(x, m) = x * evenpow(x, m - 1); pow(x, n) = if n eq 0 then 1 elsif odd(n) then oddpow(x, n) else evenpow(x, n) fi; end
変数や関数は、それを使用する前に定義する必要はない。
一般的に、節の中での定義の順番は無関係で、定義の順番を変えることは、その節の意味には何の影響も与えない。
where
節での定義では、以下のように、それ自身がwhere
節をとることが出来る:
Z * A where A = 5; B = 5; C = Z * Z where A = 3; Z = A + B; end end
一般的に、ある(変数や関数の)出現に適用される定義は、最も内側の節の定義となっている。
例えば、上式のZ
の定義において、A
の示す値は3、B
の示す値は5となっている。
ISWIMのプログラマは、PASCALのように、入れ子になっていて、階層的に構造化されたプログラムを書くことが出来る。
スコープの慣例は、PASCALやALGOLのものとまったく同じになっている。
以下は、100番目の素数を表示するISWIMのプログラムである:
primeno(100) where primeno(I) = if I eq 1 then 2 else nextprime(primeno(I - 1)) fi; nextprime(Q) = if isprime(Q + 1) then Q + 1 else nextprime(Q + 1) fi; isprime(P) = nodivsgt(2) where nodivsgt(k) = k * k > P or (P mod k ne 0 and nodivsgt(k + 1)); end end
このプログラムは、「整数ISWIM」とでも呼ばれるべきもので書かれていて、個々の変数は整数であり、関数は整数上でのものとなっている。
+
、>
、 and
といった算術演算子、関係演算子、論理演算子は通常の意味で使われ、0
と1
はそれぞれfalse
、true
という真偽値を意味している。
Landin(1966)によって記述されたこの言語は、ここまでで示した以上に精巧で強力なものとなっている。
1つには、where
節には実際には2種類のものがあって、再帰的なものはwhererec
という単語で示され、非再帰的なものはwhere
という単語で示された。
非再帰的な節では、補助定義は節の先頭の式でのみ有効だった(この先頭の式は節の主題(subject)と呼ばれる)。
LandinのISWIMは高階(higher order)でもあり、すなわち、関数は他の関数を引数とすることが出来た。
最後に、Landinは言語をより効率的にし、命令型言語をISWIMに簡単に翻訳できるようにするために、「プログラムポイント」という「汚れた」命令的な機能を追加した。
私たちのLucidはLandinの仕事を直接ベースにしているが、正確には1966年に提示された言語には基づいていない。
1つには、where
節はすべてwhererec
節となっていて、再帰的であるのがデフォルト(実際は唯一)なので、明示的に指定する必要がない。
したがって、where
という単語は元のISWIMでは非再帰的であることを示していたにもかかわらず、私たちはwhere
という単語だけを使用する。
さらに、Lucidは一階言語(first-order language)であり、関数を他の関数の引数にすることが出来ない(この制限の理由とその削除の可能性については後の章で説明する)。
したがって、LucidがベースにしているISWIMのバージョンも、一階である。
最後に、Lucidは厳密に非手続き型言語であり、「汚れた」機能は持っていない。
したがって、ベースとなったISWIMのバージョンにもプログラムポイントがない必要がある。
この区別を明確にするために、私たちは1966年に導入された、非再帰節、高階関数、プログラムポイントを使用する歴史的なISWIMを「ISWIM 66」と呼ぶことにする。
そして、命令的な機能が削除され、すべてのwhere
節が再帰的である、整えられた最新のバージョン(これは私たちが非公式に記述しただけである)を「ISWIM」と呼ぶことにする。
最後に、LucidがベースにしたISWIMの一階のサブセットを(もっと控えめに見えるように)「Iswim」と呼ぶことにする。
この本ではISWIM 66はまったく使用しない。
ということで、まずはLucidのベースになったISWIMについての導入。
(ISWIMの具体的な説明は次から)
このISWIMという言語はHaskellの祖先であり、そういう意味で、LucidとHaskellは親戚関係にあるとも言える。
純粋さ(副作用をなくし参照透過性を維持する)を求めているのも同じだし。
もっとも、その解決方法は、片や履歴変数、片やモナドと、だいぶ違うわけだけども。
今日はここまで!