動的配列がRPGにやって来た - 制約事項、回避策など
このシリーズの最初の2本の記事では、 自動的にサイズ調整される 配列 と、 サイズが変動する配列の基本的な使い方について取り上げました。シリーズ最後となる今回の記事では、現時点でのこのサポートにおけるいくつかの制約事項と、それらを回避するのに役立つようにIBMが用意してくれている機能について取り上げようと思います。
IBMは広範な制約事項リストを公開していますが、通常の使い方では、「真の」制約事項は実際的には2点だけです。もっとも、それらのリスト全体を確認される場合は、 こちらでご覧になれます。
制約事項
念頭に置いておくべき1つ目の制約事項は、現時点では、DIM(*VAR)またはDIM(*AUTO)で定義できるのは、最上位の変数のみであるという点です。これはつまり、データ構造(DS)配列または独立型配列ということです。したがって、DS 内の 配列の長さは、変動することはできません。これは、考えてみれば当然です。というのも、DS内の配列に後続するどのフィールドも、その配列の長さが拡大・縮小するのに合わせて「移動」させられることになるからです。
2つ目の(より重要な)制約事項は、可変長配列はパラメーターとしても、戻り値としても、プロトタイプで指定することができないという点です。これは、そのような配列をパラメーターとして渡すことができないということではありません。それらは、後述するように、パラメーターとして渡すことはできますが、プロトタイプでそのように定義することはできないということです。その主な理由は、RPGの動的配列には、他のILE言語に直接的な等価物がないため、配列のデータとともに現行の長さを渡すためのIBM i 公式のメカニズムがないからということになります。
IBMは、今後、こうした機能のサポートを追加するかもしれないと述べていますが、そうなるとしても当分先のことだろうと思われます。それらの実装は、RPGプログラマーがその機能をどれくらい活用するか次第、また、そのような機能強化の要望を、RPGプログラマーが時間を割いてRFEとして投稿するのかどうか次第だと思います。
パラメーターとして変数配列を渡す
パラメーターとして可変次元配列を渡すためには、プロトタイプで普通の配列として(つまり、通常のDIM(nn)キーワードで)指定されている必要があります。また、配列の初期サイズが0要素であることでコンパイラーのご機嫌を損ねないように、OPTIONS(*VARSIZE)の指定が必要となります。渡されるのは配列データのみであるため、いくつの要素がアクティブであるか判定できるように、通常は、呼び出される側のルーチンへ、現行の有効長を別のパラメーターとして渡す必要がある点に注意してください。
まず、パラメーターとして可変配列を渡す呼び出し側プログラムのシンプルな例を見てみましょう。呼び出される側のプログラムについては、後ほど見て行きます。このプログラムは、データが入れられる要素の数を尋ね、そのテスト データを生成してから、結果として生じる配列を2つ目のプログラムに渡すテスト ハーネスであることに留意してください。
(A) まず、自動的にサイズ調整される配列(*AUTO)として、要素の最大数を100にして配列を定義します。
(B) プロトタイプで配列を定義するときに次元を100(配列の最大サイズ)として指定したこと、そして、コンパイラーが最大値より小さい配列(ここでは、配列の要素数が100未満の場合)を受け入れるように、*Varsizeオプションを指定する必要があることに注意してください。
(C) ここで、プログラムはいくつの要素をロードしたらよいか尋ねてから、(D)で、要求された数を、配列が格納できる最大値と照合します。必要な場合は、要求された数の代わりに最大値が使用されます。
この例ではプロトタイプ定義で配列サイズをハード コーディングしましたが、%ELEMオプションを使用して配列サイズを設定するやり方もあったことに注意してください。そうすることにより、プログラムの柔軟性が高まります。つまり、DIM(100)とコーディングする代わりに、DIM( %ELEM( dynarray : *MAX )を使用することもできたということです。
(E)では、配列の内容を表示するDYNARRAY6Cプログラムを呼び出す前に、要求された数の要素に、生成された値を格納します。
プログラムが呼び出されるときに、呼び出される側プログラムの命令の対象をアクティブな要素に制限できるように、現行のアクティブな要素数(すなわち、%ELEM)がパラメーターとして渡されることに注意してください。
(A) dcl-s dynArray int(5) Dim( *Auto : 100 );
dcl-s count int(5);
dcl-s i int(5);
dcl-s wait char(1);
dcl-pr DynArray6C ExtPgm;
(B) array Int(5) Dim(100) Options(*VarSize);
elements int(5);
End-Pr;
(C) Dsply 'How many elements should I load?' ' ' count ;
(D) If count > %Elem(dynArray : *max ); // Use max if > max requested
count = %Elem(dynArray : *max );
EndIf;
(E) // Fill requested number of array elements with values
For i = 1 to count;
dynArray(i) = i + count; // Add new element
EndFor;
// Now call program notifying it of current element count
(F) DynArray6C ( dynArray: count );
Dsply 'All done! - Hit enter to terminate' ' ' wait;
呼び出される側のプログラムについては、特別なことは何もありません。受け取った2つ目のパラメーターを使用して配列を受け取って処理することで、実際の現行の配列サイズを超えてデータにアクセスすることが避けられます。(G)で見て取れるように、プロシージャー インターフェースはプロトタイプ定義を反映しています。そしてもちろん、お行儀よく、プロトタイプをコピー メンバーに置いて、それをここでも/copyすればよかったのですが、これは説明用の例であるため、単純さを選びました。
(H)で確認できるのは、プログラムは、呼び出し元によって供給された数を使用して、有効である要素のみにアクセスを制限していることです。渡された配列内のアクティブな要素数を超えた格納場所に何が潜んでいるかは誰にも分かりません。
dcl-s i int(5);
(G) dcl-pi DynArray6C ExtPgm;
array int(5) Dim(100);
elements int(5) Const;
End-Pi;
Dsply ( 'Received ' + %Char(elements) + ' elements');
Dsply 'Values are:';
(H) For i = 1 to elements;
Dsply ('Element ' + %Char(i) + ' has the value ' + %Char(array(i)) );
EndFor;
これで全部です、と言いたいところですが、呼び出される側のプログラムが配列に新たな要素を追加する必要があるとしたら、どうなるでしょうか。その場合、どのように処理したらよいのでしょうか。
心配無用です。IBMの優秀なスタッフたちが、この点について考えてくれています。
配列エントリーを追加する
ここで考慮すべきことは、2つあります。1つ目は、呼び出される側のルーチンがレコードを配列に追加することになる場合、必ず、それを許容するだけの十分なメモリーが配列に割り当てられているようにしておく必要があることです。2つ目に、呼び出される側のルーチンは、制御が戻ったときにその数をリセットできるように、更新されたアクティブな要素数を通知する必要があります。また、呼び出される側のルーチンが配列エントリーを削除した場合にも(それらを追加した場合だけでなく)、通知が行われる必要があることに注意してください。
2つのプログラムの中にある大多数のロジックは、前述の2つのプログラムとおおむね同じであるので、重要な相違点について説明しておこうと思います。
呼び出し側のプログラムでは、(I)で、*ALLOCオプションを指定して%ELEMを使用することで、可能性のある最大数の要素を格納するのに十分なメモリーが配列に割り当てられていることが確保されます。これは、アクティブな要素の数に影響を及ぼすことはなく、メモリー割り当てに対してのみ影響することに注意してください。
呼び出しの後で、%ELEMを使用して、呼び出される側のプログラムによって判定された要素数に比べて多少の増減があっても収容できるようにアクティブな要素数を設定します。ここで注目すべき重要なものは、*KEEPオプションです。これは、基本的には、いずれかの新たな要素に対してRPGが初期化を実行してしまうのを防止します。*KEEPオプションを指定していないと、たとえば、アクティブな要素数が呼び出しの前の10から呼び出し後に25に増えた場合に、RPGは、通常、要素11~25をそれらのデフォルト値に初期化してしまいます。その場合、呼び出される側のルーチンによって追加されたばかりの値がすべて削除されてしまうので、好ましくありません。*KEEPは、現在それらの配列スロットにある値に十分満足であり、それらを初期化してはならないことをRPGに伝えてくれるということです。
// Allocate sufficient space for the maximum elements
(I) %Elem(dynArray : *ALLOC ) = %Elem(dynArray : *Max );
// Now call program notifying it of current element count
DynArray7C ( dynArray: count );
// Reset active element count preserving new values
// that have been added by called program
(J) %Elem( dynArray : *KEEP ) = count;
呼び出される側のプログラムは、渡された配列が動的であることを知らないため、必要となる特別なロジックはありません。
こちら側では、単に配列内のすべてのアクティブな要素をループして(K)、それぞれのエントリーを順番に修正してそれらが更新できることが示されるだけです。
次いで、渡されたアクティブな要素数を増分し(L)、この値を使用してその新たな要素に値12345を設定します。新たなエントリーが加えられ、要素数の値(このプログラムにパラメーターとして渡された要素フィールド)が更新されます。この値は、上の(J)で、呼び出し側プログラムでアクティブな要素数をリセットするために、後で呼び出し元で使用される値です。
(K) For i = 1 to elements;
array(i) += i; // Increase value of each element by i
EndFor;
// Now add an extra element to the array
(L) elements += 1;
array(elements) = 12345;
まとめ
これらの新たなタイプの配列は、非常に有用なRPG言語への追加機能だと思います。長年にわたってRPGでプログラミングを行ってきた方にとっては、それらは、それほどワクワクするもののようには思われないかもしれませんが、この言語を使い始めたばかりの方にとっては、RPGが本当は優れたビジネス言語であるのにもかかわらず、そのように認識されるのを妨げている障壁をもうひとつ取り除く一助となります。
この記事シリーズを通して、これらの新たなタイプの配列の使い方や、現時点でのサポートの基本的な制約事項を回避する方法について、適切なアイデアをお伝えできたのだとすれば幸いです。質問やコメントなど、何かありましたらご連絡をいただければと思います。