連鎖性言語/連鎖は合成

スタック型言語の基本的特質は、2 つのプログラム XY の連鎖 (concatenation)、くだいて言えば XY を並べること、すなわち X Y が、X の結果に Y を適用するプログラムになるということ、すなわち XY合成 (composition) したものになる、という点にあります。このことは、よく使われる多くのクォーテーションパターンを、非常に簡潔に記述できることを意味します。

たとえば Factor では、filter コンビネータを使うことで、シーケンスを述語によってフィルタリングすることができます。

( scratchpad ) { 1 2 3 4 5 6 7 8 } [ even? ] filter .
{ 2 4 6 8 }

上とは逆に、述語に一致するものを取り除くには、not ワードを使って述語を合成できます。

( scratchpad ) { 1 2 3 4 5 6 7 8 } [ even? not ] filter .
{ 1 3 5 7 }

部分的な適用も非常に簡単です。ワードを呼ぶ前にいくつかのパラメータをスタックに積むクォーテーションを記述すればよいのです。

( scratchpad ) { 1 2 3 4 5 6 7 8 } [ 4 > ] filter .
{ 5 6 7 8 }

上に示した 3 つのクォーテーションに注目してください。

[ even? ]
[ even? not ]
[ 4 > ]

いずれも、適用型言語で記述した同様のコードより簡潔です。これは、一度しか使われない入力引数に名前を付ける必要がないからです。たとえば、Common Lisp では次のようになります。

(function evenp)
(lambda (x) (not (evenp x)))
(lambda (x) (> x 4))

連鎖性言語は名前付きパラメータを持たないので (Factor の locals ボキャブラリのことはここでは置いておきます)、自由変数についてあれこれ言っても意味がありません。連鎖性言語では、既存のクォーテーションから新しくクォーテーションを作ることが、自由変数に最も近いものだと言えるでしょう。

Factor では、クォーテーションはオブジェクトのシーケンスのように表現されます。たとえば、[ 2 2 + ] は 3 つの要素、すなわち整数 2、もう一度整数 2、そしてワード + を要素として持っています。最後の要素はそれ自体が、イントロスペクトして実行できるオブジェクトです。このことは、シーケンス操作を使ってクォーテーションを作成できることを意味します。

たとえば、クォーテーション [ + ] があったとします。このクォーテーションが実行されると、スタックから 2 つの数値を取り出し、この 2 つの数値を足して、結果をスタックに置きます。このクォーテーションの前に整数 5 を前置すれば、[ 5 + ] という新しいクォーテーションができます。このクォーテーションは、スタックの一番上の値に 5 を足します。これは適用型言語におけるカリー化に似ています。実際、Factor ではこの操作を curry と呼びます。

クォーテーションに対するもうひとつの基本操作は、合成 (composition) です。たとえば、[ 2 + ] というクォーテーションと [ 0 > ] というクォーテーションがあったとします。こららのクォーテーションの合成は、[ 2 + 0 > ] になります。もちろん、これは 2 つのクォーテーションの連鎖 (2 つのクォーテーションを並べただけ) に過ぎません。

これら 2 つの操作、すなわち currycompose は、きわめてシンプルで直観的に理解しやすいセマンティクスを持っています。なお、クォーテーションは printable なオブジェクトですが、適用型言語ではクロージャは一般に opaque です。

次に示すのは、curry の例です。

( scratchpad ) 5 [ + ] curry .
[ 5 + ]

適用型言語のコードと比べてみてください。クロージャは読みやすい形では表示されません。

* (let ((x 5)) (lambda (y) (+ x y)))
#<FUNCTION (LAMBDA (Y)) {11682335}>

次に示すのは、compose の例です。

( scratchpad ) [ 3 = ] [ not ] compose .
[ 3 = not ]

適用型言語のコードです。

* (let ((f (lambda (x) (= x 3)))) (lambda (y) (not (funcall f y))))
#<FUNCTION (LAMBDA (Y)) {11680B3D}>

以上のことからわかるように、スタック型言語では関数の合成と部分的適用をはるかに自然な形で行うことができます。これはスタック型言語が、合成 (composition) と連鎖 (concatenation) という両特性を備えているためです。

This revision created on Wed, 31 Dec 2008 09:32:01 by mnestic (formatting)