†REXXのどろぬま†

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

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

基本的に、REXXの関数では引数のアドレス渡しができない。 したがって、関数の演算結果を引数に戻すことはできない。 演算結果は戻り値(1つのみ)か、グローバルに晒された変数にしか渡せない。 このため、二つの変数の値を交換するような単純な関数の自作すら難しくなる。 戻り値は1つしかないので、交換後のx,yの二つの値を戻すことはできない。 x,yをグローバルに晒すと汎用性がなくなる上に、変数名の重複の危険性が出て来る。

しかし、VALUE関数を用いると、変則的ながらアドレス渡しに近い操作が可能になる。 それが、SysFileTree('*.*','file','FO')'file'の部分でも使用されている手法、 すなわち、変数を文字列として渡すというトリッキーな方法だ。 論より証拠で、交換関数SWAPを自作すると、次のようになる;

/* SWAP関数のサンプル*/

x=10
y=20
CALL SWAP 'x', 'y'	/* x, yではなく'x', 'y' */
say x			/* 20になる */
say y			/* 10になる */

EXIT

SWAP:
  _swap_arg1=ARG(1)			/* 'x'を取得 */
  _swap_arg2=ARG(2)			/* 'y'を取得 */
  _swap_val1=VALUE(ARG(1))		/* xの値を取得 */
  _swap_val2=VALUE(ARG(2))		/* yの値を取得 */
  CALL VALUE _swap_arg1, _swap_val2	/* xにyの値を設定 */
  CALL VALUE _swap_arg2, _swap_val1	/* yにxの値を設定 */
RETURN 0
CALL SWAPの引数が「x,y」ではなく、「'x','y'」であるのが重要なポイント。 「x,y」では変数の中身である「10」と「20」が渡されるが、「'x','y'」ならば変数「x」と「y」そのものが渡される。 また、SWAP関数内で引数の具体的な変数名(ここではx,y)が出てこないのがミソ。

このVALUE関数は「変数」ではなく、「変数名」に対して参照・代入を行う、 言わば変数の間接操作を可能にする関数。 たとえば;

x=10
SAY VALUE('x')		/* 10と表示される */

CALL VALUE 'x', 20
SAY x			/* 20と表示される */
てな感じで使用する。一見すると、普通の変数の参照・代入と大差がない。 変数をわざわざ文字列扱いにして操作する意味がわからん…と言うことになるのだが、 実はこれこそ、まさに今回のような場合のための機能。 関数の引数として、変数の値ではなく、変数名(文字列)を渡すことで、 変数自体が操作対象になる。これが、疑似アドレス渡しである。

ここでもう一点重要なのは、変数のスコープの問題。 VALUE関数で変数自体の操作が可能になっても、 それは変数スコープ内になければ意味がない。 したがって、上記SWAP関数はローカル化(procedure)されていない。 さらに言えば、操作対象の変数がなんであるかは、 SWAPがコールされるまで不明なので、 予めPROCEDURE EXPOSEでその変数のみ晒すということもできない。 完全に晒しておかないとダメである。

完全晒しでは、グローバル変数を使うのと変らないのでは? つまり;

x=10
y=20
CALL SWAP
SAY x
SAY y

Exit

SWAP:
  tmp=x
  x=y
  y=tmp
RETURN 0
と同じじゃね!?というハナシになってしまう。 ところが、二つのコードを比較すれば判るように、疑似アドレス渡しの方では、 メインルーチンに依存したり、重複したりするような変数名は一切使われていない。 VALUEで変数の間接参照が可能になったおかげで変数名の抽象化が可能になり、 グローバル変数であっても、メインルーチン側の変数名に依存しないで済んでいる。 つまり、交換する変数がxとyではなく、aとb、mydataとyourdataのように、任意の変数名でも機能する。ここは非常に大きな違い。

また、SWAP内で使用する変数名は「_swap_」で始まると決めたことで、 (自分でルール破りをしない限り)メインとの変数名重複も起きなくなった。 つまり、グローバルに晒していても、疑似的にローカル化されているのと同等である。

ただし、これはこのSWAP関数のように、処理内容がごく単純な場合のハナシ。 より複雑な処理を行うルーチンで、すべての変数に「_swap_」を付けるのも大変だ。 そこで、複雑な処理のルーチンでは、インターフェース部と処理部を分離し、 処理部のみローカライズする。 たとえば;

/* インターフェース部分離サンプル */
x=10
y=5
CALL MYSUB 'x', 'y' /* 変数x,yに適当な演算をして、演算結果をx,yに戻す*/
say x y
EXIT

MYSUB: /* インターフェース部はグローバルに晒す */
  _mysub_arg1=ARG(1)
  _mysub_arg2=ARG(2)
  CALL MYSUB_MAIN
RETURN 0

MYSUB_MAIN: /* メイン部(処理部)はローカライズする */
PROCEDURE EXPOSE VALUE(_mysub_arg1) VALUE(_mysub_arg2)
  a=VALUE(_mysub_arg1)
  b=VALUE(_mysub_arg2)

  Do n=1 to 8 	/* 処理は適当 */
    a=a+b	/* ローカライズされているので */
    b=a/b	/* nやaやb等の汎用変数が使用可 */
  End

  CALL VALUE _mysub_arg1, a
  CALL VALUE _mysub_arg2, b
RETURN 0
と言った感じになる。インターフェース部(MYSUB)は全て晒しになるが、処理部(MYSUB_MAIN)はローカライズされており、nとかaとかxとか、汎用的な変数名を使用しても問題ない。判りにくいのは、EXPOSEする場合のVALUEと、変数の中身を参照する場合のVALUEでは、意味が異なること。上記の例では以下のようになる。

EXPOSE VALUE(_mysub_arg1) → 変数名「x」を意味する
a=VALUE(_mysub_arg1) → 変数「x」の中身の「10」を意味する

なお、SysFileTreeの例でも判るように、この方法は配列(ステム変数)に対しても有効。戻り値が多い場合は、配列を使用する方がよいだろう。


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