連鎖性言語/複数の戻り値

スタック型言語では、適用型言語より容易に複数の戻り値を扱うことができます。適用型言語での典型的なアプローチは、複数の値を 1 つのタプルにパッケージ化することです。この方法はスタック型言語でも使えますし、複数の値を 1 つの単位として扱いたい場合 (たとえば 3 つの要素からなる 3 次元の点など) には便利です。しかし一般的には、2 つ、ないし 3 つの項目をスタックに積めば、それだけで目的を達成できます。

Factor では、まったく値を返さないか、または単一の値を返すワードがほとんどですが、必要なら複数の戻り値を簡単に扱うことができます。ここでは、複数の戻り値があると便利な場合を例に取り上げましょう。at ワードは 1 つのキーとハッシュテーブルを取り、1 つの値または f を返します。これは Java や Python のハッシュテーブル探索と似ており、問題を 1 つ抱えています。それは、ハッシュテーブルにキーがない場合と、キーの値が f の場合とを区別する方法がない点です。Factor ではもうひとつ、at* というワードがあります。at* は 2 つの結果を返します。一つは値で、もうひとつはその値が存在するかどうかを表すブール値です。at* を使うと、ハッシュテーブルにキーが存在しない場合には f f が返され、キーが f に設定されている場合には f t が返されます。キーが存在するかどうかを表すブール値はスタックの一番上にあるので、これを使ってチェックを行うことができます。

at* [ ... handle value ... ] [ ... no value ... ] if

実際、at ワードは at* を使って次のように定義されています。

: at ( key assoc -- value ) at* drop ;

最初に簡単な例を取り上げましたが、Haskell のパターンマッチングを使えば、あるいは Common Lisp の複数の値のサポートを利用しても、多少コードは長くなるものの (一度しか使わない 2 つの出力値に名前を付ける必要があるため)、同じ操作を簡単に実現できます。

さて、もっと興味深い例として、条件判定のそれぞれの分岐部分で 2 つの値が出力され、これらの値が別のワードで消費されるケースを考えてみましょう。

[ foo ] [ bar ] if +

ここで、foobar は 2 つの値を出力し、+ は値を 2 つ消費します。最初に挙げたのは、null があっても安全にオブジェクトのメンバにアクセスする例でしたが、今回のパターンもスタック型言語ではそのまま直接的に表現できるのに対し、適用型言語ではいろいろと回り道をする必要があります。

おそらく複数の戻り値の応用例として最も変わっているのは、「モディファイヤ」(modifier) というコンセプトでしょう。たとえば、do-thisdo-that、および do-it という互いに似通ったワードがあり、いずれも同じ入力パラメータ X、Y、Z を取るとしましょう。このとき、新しいワード frob を書くことができます。frob は、X、Y、Z を入力に取り、これらに変更を加えて出力します。これで、次の 7 つの操作が可能になります。

do-this
do-that
do-it
frob
frob do-this
frob do-that
frob do-it

しかし、定義したのは 4 つのワードだけです。

具体的な例を出します。Factor には、シーケンスの一部を抽出する 2 つのワード、subseq<slice> があります。どちらも同じ 3 つのパラメータ、すなわち開始位置、終了位置、およびシーケンスを取ります。2 つのワードの違いは、subseq が要素を新しいシーケンスにコピーするのに対し、<slice> は、基になるシーケンスのビューに相当する「バーチャル」シーケンスを作成する点にあります。したがって <slice> では、基になるシーケンスが変更されると、その変更はビューにも反映され、追加のメモリを必要としないといった違いがあります。ただし多くの場合、必要になるサブシーケンスは、シーケンスの開始位置または終了位置を起点とするものがほとんどです。そのため Factor では、先頭位置と末尾位置、そして同一のシーケンスをパラメータに取る (head)(tail) というワードが用意されています。(head) ワードは常に開始位置 0 から出力を開始し、(tail) ワードはシーケンスの長さに一致する終了位置で出力を終えます。このほかによく行う操作として、最初の 5 つの要素ではなく、最後の 5 つの要素を取得したい場合があります。この場合は、モディファイヤワード from-end を使います。Factor のライブラリでは、これらの subseq<slice>(head)(tail)、および from-end を使って、以下の 8 つの高水準ワードが定義されています。

: head ( seq n -- subseq ) (head) subseq ;
: tail ( seq n -- subseq ) (tail) subseq ;
: head* ( seq n -- subseq ) from-end head ;
: tail* ( seq n -- subseq ) from-end tail ;
: head-slice ( seq n -- slice ) (head) <slice> ; inline
: tail-slice ( seq n -- slice ) (tail) <slice> ; inline
: head-slice* ( seq n -- slice ) from-end head-slice ; inline
: tail-slice* ( seq n -- slice ) from-end tail-slice ; inline

さらにもうひとつ、short というモディファイヤがあります。short は、たとえばシーケンスのうち 5 つの要素を取得したいといったケースにおいて、シーケンスの要素数が 5 つに満たないときは、境界外エラーではなく、すべての要素を受け取るようにしたい場合に使用します。short は、上にリストアップしたワードを対象とするモディファイヤで、次のように実装されています。

: short ( seq n -- seq n' ) over length min ; inline

したがって、これまでに取り上げた 8 つのワードと short を利用することで、16 の異なる操作が可能になることになります。しかも、追加のコードはほとんど不要で、本来のワードとして必要なのはわずか 6 つだけです。

This revision created on Sun, 4 Oct 2009 15:14:42 by DK (frob is as well an operation)