いものやま。

雑多な知識の寄せ集め

「Lucid, the Dataflow Programming Language」を翻訳してみた。(その7)

だいぶ間が空いてしまったけど、久々に。

2. ISWIMと代数

Lucidは定義型言語(definitional language)である。
すなわち、Lucidプログラムの文(statement)はストリームとフィルタを定義する式であり、格納場所を更新するコマンドではない。
Lucidはこの意味で珍しい言語ではあるが、別に独特なものだというわけではない。
最初の定義型言語であるMcCarthyのLISPは、1960年、つまり約25年以上前に設計されたものである。

LISPプログラム(あるいは、少なくともLISPの「純粋な(=副作用を持たない)」サブセットのプログラム)は、値を求めたい式(expression)を伴った、副作用のない(side-effect-free)関数の定義の集合(これらは式で使われる)である。
純粋な「コア」言語には命令的な機能は含まれていないため、その意味論(semantics)は、式の値がその部分式の値にのみ依存するという意味で、参照透過(referentially transparent)となっている。
残念なことに、純粋なサブセットで書かれたプログラムは書くのが非常に面倒であり、また、容認できないほどに実行が非効率的だった。
後のバージョンの言語では、効率を改善するために命令的な機能(prog形式やreplacareplacd操作など)が追加された。
副作用が可能になり、それらはすぐに無慈悲にも濫用された。
それゆえ、参照透過性は失われ、言語の数学的な美しさは失われた。
これらの「汚れた」機能によって得られた時間と空間の大幅な節約は、純粋な適用型(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

dr1r2を定義する式に現れ、その値は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

以下のような、powevenpowoddpowの相互再帰的な定義も許されている:

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といった算術演算子、関係演算子、論理演算子は通常の意味で使われ、01はそれぞれfalsetrueという真偽値を意味している。

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は親戚関係にあるとも言える。
純粋さ(副作用をなくし参照透過性を維持する)を求めているのも同じだし。
もっとも、その解決方法は、片や履歴変数、片やモナドと、だいぶ違うわけだけども。

今日はここまで!