メニューボタン
IBMi海外記事2020.10.14

動的配列がRPGにやって来た - 第2部

Jon Paris 著

このトピックに関する最初の記事 では、新たな動的配列の自動サイズ調整オプション(*AUTO)を取り上げました。今回の記事では、2つ目のオプション(*VAR)について見て行きます。*VARでは、配列のキャパシティーをプログラマーが直接管理できます。つまり、必要に応じて、配列を拡大および縮小させることができます。また、3つ目のオプション(*CTDTA)についても簡単に取り上げるつもりです。これは、お察しの通り、コンパイル時配列に関するものです。

可変長配列の使用法

まずは、サイズが変動する配列をSQLと組み合わせて使用する短い例を見てみましょう。この手法は、「SQL結果セットを格納するためには配列をどれくらいのサイズにするべきでしょうか」という昔ながらの問いに対する答えとなるものです。この新たな動的配列のサポートにより、取得される行数に応じて配列のサイズを動的に設定することによって、この問題に対処することができるようになりました。

前記事では、現在の配列の要素数、または配列に現在割り当てられているサイズでいくつの要素を格納できるかについて確認するのに、%ELEMを使用しました。%ELEMを必要としたのは、*AUTO配列では、プログラマーは間接的に最大キャパシティーを制御しているだけだからです。この記事では、%ELEMを使用して、直接、配列のサイズを設定します。

以下にコードを示しますが、その前にいくつか説明を記します。

(A) まず、データ構造(DS)配列を、タイプ*VARとして、絶対的な最大サイズ32,000要素で定義します(実際には、組み込みSQLの実際的な最大値は現時点では32,763ですが、32Kのほうがタイプしやすいのでそうしました)。簡略化するために、DSはこの例で使用している表を基にして定義されています。

(B) カーソルはオープンされていて、行がフェッチされる前に、正確な行カウントを取得するためには、「インセンシティブ」で「スクロール可能」なカーソルとして宣言する必要があります。

(C) カーソルがオープンされたら、GET DIAGNOSTICSを使用して行カウント(DB2_NUMBER_ROWS)を取得し、その値をホスト変数countへロードできるようになります。

(D) ここで魔法が起こります。すなわち、ここでcountを使用して、配列のアクティブな要素の数を設定します。ここより前の時点では、その値は0であったはずです。

(A)    dcl-ds  dsArray  ExtName('ANIMALS')  Dim(*Var : 32000)  Qualified;
       End-ds;

       dcl-s  i  int(5);
       dcl-s  count  Int(5);
       dcl-s  type  Like(dsArray.type);

       Dsply 'Search for type? ' ' ' type;


       DoU type = 'exit';

(B)       exec SQL Declare ACursor insensitive scroll Cursor for
                                   Select * From ANIMALS where TYPE = :type;
          exec SQL Open ACursor;

(C)       exec sql get diagnostics :count = DB2_NUMBER_ROWS;

(D)       %Elem(dsArray) = count;

          If count > 0;  // If there are rows load the array
(E)          exec SQL Fetch ACursor for :count Rows Into :dsArray;
          Endif;

(F)       exec SQL Close ACursor;

          // Process the result set in the array or issue "Not found" message
          If count > 0;  // Show results
(G)          for i = 1 to count;  // Loop through all results
                Dsply ( 'Index ' + %Char(i) + ' name is ' + dsArray(i).NAME );
             EndFor;
          else;
             dsply ( 'No data for type ' + type );
          endif;

          Dsply 'Enter type (exit)? ' ' ' type;

       EndDo;             *InLr = *on;

(E) 配列が結果セット全体を格納するのに十分なサイズとなったので、それらの行を配列へフェッチすることができます。

(F) データが取得されたら、別の照会の準備のためにカーソルを閉じることができます。

(G) 最後に大事なこととして、以前に取得した行カウントを使用して結果をループして表示するか、または、適切な「見つかりません」メッセージを出力します。

  • 1つ目のメリットは、おそらく最も重要なものですが、メモリー使用量が削減されることです。従来の手法では、取得された行数が、配列のキャパシティーいっぱいまで近付くことはほとんどないとしても、常に全メモリー量が使用されます。配列サイズを低く抑えることによってメモリー使用量を減らそうとした場合、ある日突然、サイズ エラーによってプログラムがクラッシュしてしまうリスクが高くなります。
  • もうひとつのメリットは、配列についてそれ以降に行われるすべての処理が、完全な配列に対して行われるようになるということです。%LOOKUP、SORTAなどの命令を使用するときに、%SUBARRを使用して範囲を制限する必要はありません。結果として、ロジックがよりクリーンに、より理解しやすくなります。

アクティブな要素の数を動的に制御する

上の例では、%ELEMを使用して、プロセスが反復される開始地点で、配列のアクティブな要素の数を設定しました。しかし、そうする必要があるのだとしたら、配列のサイズを調節することはいつでも可能です。たとえば、製品コードに対する入力データをスキャンするプロセスがあるとします。新たな製品(すなわち、まだ配列内にないもの)に出くわすたびに、それ以降の処理で追加することができます。

私の場合、おそらく、ほぼ毎日、約100前後の異なる製品を処理するだけですが、時々(クリスマス シーズンなど)、急に1,000を超えることもあります。このような状況では、まずは通常時の数(100)を収容できるように配列サイズを設定しておいて、エントリー数がその値を超えたら、その後で制限値を上げるように設定することもできます。

以下のコードは、そのような状況を処理する方法の簡略化された例です。配列の現行キャパシティー内に収まっているかどうかを常にテストするロジックを追加する必要はなく、MONITORブロックを使用していることに注目してください。

(H) 最初に注意しておくべき点は、定数indexOutOfRangeの定義です。この後すぐに目にするように、これはMONITORブロックで使用され、配列の現行キャパシティーを超えて指標値を使用しようとすると検出されます。

(I) ここでは、配列の現行キャパシティーを表示するだけです。0であるはずです。

(J) これは、単純に配列への値の配置を試みます。初期キャパシティー値は0であるため、これにより例外がトリガーされることが予期されます(実際、そうなります)。

(K) ここでは、範囲外エラーのみを捕捉します。汎用的なON-ERROR節をコーディングしなかったため、他のエラーはどれも、通常のデフォルトRPGエラー パスに従います。

(L) 範囲外エラーを検出したら、%ELEMを使用して、新たな制限値を現行値に10をプラスした値に設定することによって、配列キャパシティーを増やします。つまり、今回の新たな要素だけでなく、さらに今後9つの要素を追加できるようになります。

(M) 配列キャパシティーが増えたので、新たな要素を安全に追加することができるようになりました。

これですべてです。疑問に思っておられるといけないので言っておきますが、もちろん、配列キャパシティーは増やすだけでなく、減らすこともできます。キャパシティーを減らす例については、このシリーズの次の記事で見て行く予定です。

       dcl-s  dynArray  int(5)  Dim( *Var : 2000 );

       dcl-s  i  int(5);

(H)    dcl-c  indexOutOfRange  121;


(I)    Dsply ( 'Starting with ' + %Char(%Elem( dynArray )) + ' elements');

       for i = 1 to 21;

          Monitor;
(J)       dynArray(i) = i;  // Try to add new element

(K)       On-Error indexOutOfRange;
             // Current array capacity reached so increase by 10
(L)          %elem( dynArray ) = %elem( dynArray ) + 10;
             Dsply ( 'Item ' + %Char( i ) + ' capacity now '
                   +  %Char( %Elem( dynArray  )) );
             // Now we can add the new element
(M)           dynArray( i ) = i;
          EndMon;
       EndFor;       

終わりに

コンパイル時配列の常用者に向けては、3つ目のタイプの動的配列が利用可能です。このタイプの動的配列を使用するには、DIMをDIM(*CTDTA)のようにコーディングします。*CTDTAは、ソース内にあるコンパイル時のデータ要素の数に基づいて、自動的に配列をサイズ調整します。私の場合、コンパイル時配列はまったく使用しないため、このオプションにワクワクすることはないのですが、使用される方にとっては、非常に有用だと思います。コンパイル時データに新たな要素を追加するたびに、必ず配列定義へ戻るようにする必要はなくなります。コンパイラーが新たなサイズを計算してくれます。もちろん、配列ループ制御のためにハードコーディングされた制限値ではなく%ELEMを使用しているのでなければ、これはあまり役に立ちません。

次回予告

このシリーズの最後の記事では、この新たな配列サポートのいくつかの制限と、それらを回避する方法について見て行きます。また、これら最初の2つの記事を読まれて何か不明な点があるようでしたら、それらにお答えしたいとも思っています。

あわせて読みたい記事

PAGE TOP