†REXXのどろぬま†

ユーザー定義関数の複数戻り値

作成開始日 2017.04.30
最終更新日 2017.07.13

REXXで、座標計算、複素数演算、変数の交換など、複数の値を返す関数を自作するのはけっこう厄介。通常の意味での戻り値は1つのみであるし、引数のアドレス渡し(参照渡し)ができないので、引数に戻り値を返すこともできない。無論、変数をグローバルに晒せば戻り値の数は問題にならないが、それがリスクの高い選択である事は言う迄もない。

が、対処方法がないではない。

●戻り値連結

まず、極めて単純な方法として、複数の戻り値を文字列として連結してしまうという方法がある。つまり、「10」と「20」という二つの戻り値が欲しかったら、'10_20'という文字列を返すようにして、それを文字列処理関数(SUBSTRやPOS)で分離して使用するというもの。RexxUtilのSysCurPOS関数は、まさにこの方法を取っている(デリミタはスペースだが)。ただし、これは表現としてのスマートさに欠ける。たとえば、二つの変数を交換する関数「SWAP」をこの方法で書いたとすると、使用時には;
z=SWAP(x,y)
x=LEFT(z,POS('_',z)-1)
y=SUBSTR(z,POS('_',z)+1)
……
SWAP: Procedure
a=ARG(1)
b=ARG(2)
RETURN b'_'a
みたいな感じのソースになる。見た目もカッコ悪いし、実行時のソースも三行必要で、普通に変数交換するのと変らない(tmp=x; x=y; y=tmp)。処理にもよるが、けっこう幼稚な印象を与える。が、PARSE VALUEを使うと、この処理はぐっとスマートになる。
PARSE VALUE SWAP(x,y) WITH x '_' y
……
SWAP: Procedure
a=ARG(1)
b=ARG(2)
RETURN b'_'a
変換の実効部の記述は1行で済む。無論、Cなどのアドレス渡しが可能な言語に比べれば垢抜けない方法だが、最初の例よりはかなりマシだ。少なくとも、SWAP程度の関数であれば、これで十分な気がする。また、極端な話、変数交換に限定すれば;
PARSE VALUE (x||'_'||y)  WITH y '_' x
と言った記述も可能だ。この場合はSWAP関数を定義する必要すらない。が、戻り値がより多く、処理が複雑で、文字列などを含むものだったりすると、これらの方法では少々キツいだろう。特に最後の方法は可読性にも難がある。

●戻り値配列決め打ち

もう一つ簡単な方法は、戻り値配列決め打ち方式。つまり、特定のステム変数を関数の戻り値参照専用にするというもの。外部関数の戻り値だって、「rc」という特別な変数に決め打ちしているのだから、これをそのまま配列に拡張してしまおうという発想。すなわち、ユーザーが適当なステム変数(たとえばrc.)を戻り値専用ステム変数と決めて、複数戻り値が必要な関数では、常にこのステム変数をexposeすることにする。逆に、rc.を他の用途には絶対に使用しない。
/* 戻り値配列を晒す */

x=10
y=20
CALL SWAP x, y
x=rc.1
y=rc.2
SAY x y

EXIT;

SWAP: PROCEDURE EXPOSE rc.	/* rc.は常に晒す */
  x=arg(1)
  y=arg(2)
  rc.0=2	/* 戻り値の個数 */
  rc.1=y
  rc.2=x
RETURN rc.0
ま、この方法もSWAPが3行になるという点は変らないが、処理としては若干エレガントである。

●PIPE経由での受け渡し

もう一つの方法は、pipeを使ってpush/pullで値を受け渡す方法。これについては[コマンドライン出力の取り込み]で詳述している。外部ルーチンと複数のデータをやりとりをする場合には非常に便利な方法だが、内部ルーチンならば戻り値配列決め打ち方式の方が良いだろう。

●VALUE関数による疑似アドレス操作

これが本命。実は、REXXでも疑似的に引数のアドレス渡しが可能なのだ。これについては[VALUE関数による疑似アドレス操作]で詳述する。


【REXXのどろぬま目次】 【ホーム】