RPGの可変次元配列 | IBM i 総合情報サイト

【第5回】RPGの可変次元配列


IBM i 7.4の発表に伴い、可変次元配列(訳注)、新しいデータ定義キーワードおよびその他多くの機能が使えるようになりました。

2019/06/01 ジョン・パリス

RPGプログラマー にとってこれは春のクリスマスです。RPGの新しいリリースが出るといつも、サンタ・バーバラ**(「バーバラ」サンタさん)は今度は何を持って来てくれたのだろうと何やらクリスマスに似た期待をしてしまいます。それがどのような物なのか厳密には分かりませんが、それが役に立つものであり、ワクワクして包みを開けるであろうことは間違いありません。
(**「サンタ・バーバラ」という地名を持ち出したのを奇妙に思われる方も居られるでしょう。バーバラ・モリスという女性がRPGコンパイラーのチーム・リーダーであり、チーフ・アーキテクトであると言えば、少しはご理解いただけるでしょうか。)

IBM i 7.4の発表で、サンタは確かに私たちを落胆させたりはしませんでした。私たちは、長い間待ち望んでいた素晴らしい新機能(つまり、動的配列あるいはIBMが好む呼び名では「可変次元配列」)を手にしました。加えて、SAMEPOSという新しいデータ定義キーワードも手にしました。これにより、OVERLAYの制約なしに容易にデータ項目を再定義することができます。大事なことを言い忘れましたが、PSDSに2つの新しいフィールドも追加されています。SAMEPOSとPSDSの機能強化については後程もう少しお話するとして、まずは配列の機能強化に焦点を合わせましょう。

動的配列

話を始める前に、これらの配列の機能強化は7.4だけで利用可能であることを指摘しておかなければなりません。PTFでこれらの機能が7.3で利用可能になる予定はありません。ですから、この新しい機能が使えるようになるまで暫く待たなければならないかもしれませんが、待つ価値はあるでしょう。

もしあなたが私たちと同じであれば、配列を準備するときにはいつも、配列の大きさについて必要になると考え得るあらゆる可能性を織り込んだ設定にするか、もっと現実的な制限を設定するか、しばしば葛藤することでしょう。前者はメモリーの浪費というリスクを、後者はプログラムの異常終了というリスクを抱えています。もちろんそれに加え、どのような選択をしたとしても、そのサイズでは不十分になる日がいつかやって来ることを知った上で、私たちは否応なく選択をしています。

それは、もはや過去のものです。私たちは、今やRPGがアクティブなコンテンツを保持するのに十分なメモリーしか使用しないということをしっかり理解した上で、配列の最大サイズを大量ながらも適切な値に設定できます。さらに良いことに、RPGは配列で使用中の最大の要素を記録しているので、命令をアクティブな要素に限定するために%SUBARRを使うことを考えずに、SORTAおよび/または%LOOKUPのような命令を使用できます。それだけでもこの機能を使用する正当な理由になります。

動的配列のタイプ

ところで、あなたはこれらの配列の新しいタイプをどのように規定しますか。基本的に*VAR (可変)と*AUTO(自動)の2つのタイプがあります。自動タイプの場合、RPGは使用する最大の索引値を保持するのに十分な大きさの配列を確保します。もし、現在配列に5つのアクティブな要素があり、25番目の要素に値を割り当てるとすると、RPGは自動的に配列のサイズを25に増やします。

これらの配列定義の構文は次のようになります。

     ArrayName  DIM ( タイプ : 最大要素数 ) 

最大要素数の値は、配列がこれまでに達した 最大サイズです。これをプログラムロジック(またはデータ)エラーを避けるためにプログラムがばかばかしいほど大きな索引値を使わないようにするための「安全値」と考えます。従って、配列は次のように定義されます。

     Dcl-S  myAutoArray Char(10)  Dim ( *Auto: 1000 ); 

次の命令コードが実行されると、配列はアクティブ要素0個から始まり900個まで大きくなります。

    myAutoArray(900) = 'Highest';

しかし、1,000という上限値以上の要素にアクセスしようとするとエラーになります。これはまさにDim(1000)と定義された旧来のRPGの配列の場合と同じです。
これを、次のように定義された配列と対比してみましょう。

    Dcl-S  myVarArray  Char(10)  Dim ( *Var: 1000 );

この場合、プログラマーが何の指示もしなければ、索引値1の配列要素を使用しようとしただけでエラーになります。その理由は、可変タイプの配列はアクティブな要素の数は0から始まり、増加するよう指示された場合にだけ増加するからです。では、どのように増加の指示をするのでしょうか?それには古くからある組み込み関数%Elem()の新たな役割を使用します。今や、式の左辺に%Elem()を使うことで、アクティブな要素の数を設定できます。ですから、900番目の要素に値を入れたければ、先ず次のようにコーディングしなければなりません。

    %Elem( myVarArray ) = 900;

その後、次のようにコーディングできます。

    myAutoArray(900) = 'Highest';

これらの新しい2種類のタイプの配列のどちらに対しても、%Elem()が返す値は旧来の配列に対するそれとは少し違います。これらのタイプの配列の場合、戻り値は最大サイズではなく現行のアクティブな要素の数です。任意の時点で配列の絶対的最大値を知る必要がある場合には、*Maxというキーワードを指定すればその値が得られます。

    If  index <= %Elem( myAutoArray : *Max );

*NEXT

*AUTO(自動)配列はもう1つ別の素晴らしい機能を提供します。実は、使用された最大の索引値の経過を追う必要がありません。索引値を単に*Nextと指定するだけで、RPGがそれをやってくれます。ですから、次のようなコーディングを行うことができます。


      Dcl-S  myAutoArray Int(5)  Dim( *Auto : 999 );

      Dsply ('Start: Active elements = ' + %Char(%Elem(myAutoArray)) );

       For i = 1 to 50;
          myAutoArray( *Next ) = i;
       EndFor;

       Dsply ('End: Active elements = ' + %Char(%Elem(myAutoArray)) );
       Dsply ('Maximum capacity of array is: '
              + %Char(%Elem(myAutoArray : *Max)) );

これは他の大半の現代的プログラミング言語が配列を取り扱う方法と遥かに調和しており、私たち全員とりわけRPGを初めて学ぶ人たちを少し楽にしてくれます。

(まあまあ)動的な配列

動的配列には2つのタイプがあると先に述べましたが、実はそれほど動的ではない3つ目の変異形があります。このオプションは*CTDATAキーワードによって要求され、多分あなたの推測通り、コンパイル時のデータ配列に関係しています。配列の要素数を指定する代わりに、単に*CTDATAを指定すると、コンパイラーはコンパイル時に見つけたデータ項目の数に基づいて配列サイズを設定します。それは良い機能ですが、正直私たちはコンパイル時配列が嫌いで、その気持ちを凌駕するには不十分です。ですからこの機能についてはパスします。

制約およびその他の考慮事項

この新しいサポートは遍在するものではありません。つまり、それは(まだ)Dim()が指定できるあらゆる場所で利用可能とは限りません。現在のところ、それは最上位レベルの定義、すなわちスタンドアローン配列またはDS配列でしか使えません。例えば、DS内の入れ子になった配列に対しては使えません。また、パラメーターを定義するのにも使えません。これらの配列をパラメーターとして渡すことはできますが、考慮すべき多くの留意事項や追加オプションがあります。これらについては、後日別の記事で論じる予定です。

もう1つの追加の制約は、可変次元配列は旧スタイルの演算仕様書では使えないということです。これは実際のところ大きな制約ではありません。なぜなら、過去18年間コーディング・スタイルをフリーフォームに変えていない人たちは、そもそも可変次元配列のような現代的な何かを使う可能性がないからです。

これは制約と言うよりもむしろ警告ですが、現行の配列サイズを増やす場合はいつでもシステムが配列をメモリーに移動する必要があるかもしれないことを理解している必要があります。結果、(例えば、CスタイルのAPIに配列を渡すため)組み込み関数%Addr()を使ってそのアドレスを取得する場合、使用する前にそのアドレスをリフレッシュしなければなりません。さもないと間違ったデータを渡す危険があります。

同様に、デバッガーは配列サイズが変わるかもしれないことに全く気付かないので、デバッグの最中に現行サイズを超えて配列要素にアクセスしないようにしなければなりません。さもなければ、あなたは混乱することになります。RPGは、現在の最大値を保持し現在の配列要素数を確かめるためにデバッグ中に表示可能な”_QRNU_VARDIM_ELEMS_配列名”という特殊値を提供しています。しかしこれは真の値のコピーであり、その値をデバッグ中に変えても効果がないことに注意してください。

今使える拡張機能

IBM i 7.3でも使えるようにPTFが作られている拡張機能について見てみましょう。実際、それらのPTFは既に利用可能です。 PTFの詳細についてはこちらこちらを見てください。

SAMEPOS

このキーワードは新しい機能を提供するわけではありませんが、ある種の定義を実装するのが容易になり、 後の人にとってその定義がより明確になります。例えば、2003年に遡りますが、私たちは「D仕様書発見」という記事を書きました。

その記事で、連続するフィールドを配列として再定義するテクニックについて論じました。それらのテクニックは依然として機能しますが、新しいSAMEPOSキーワードは、物事をもっと単純にしてくれます。12個の連続するフィールドをもつファイルがあるとしましょう。その1つ1つは1年の各月に対応するものとします(JanSales、FebSales等々)。それらの月次売上高を配列として扱えるようにするには、次のようにファイルをベースにした外部記述のDSをコーディングするだけでよいのです。

         MonthlySales  Like(JanSales)  Dim(12) SamePos(JanSales);
     End-Ds;

簡潔ですよね。SAMEPOS キーワードは項目の開始位置を識別しますが、POSキーワードあるいは昔の開始/終了の表記法と違い、ソフトコードです。その結果、データのレイアウトが変わっても、次のコンパイルで自動的に位置の調整が行われます。これはずっと安全なやり方です。

この機能が重宝する場面はほかにも沢山あります。最近私たちが直面したのは、S/36スタイルの見出し/明細レコードを1つのファイル内に持つような複数フォーマットのレコードを処理する効率的な方法を探すのに関係するものです。こうしたことは思うほど稀ではなく、メインフレームのコンバージョンやある種のEDIレコードでも見かけるものです。次に示すのはS/36スタイルのファイルの例で、I仕様書、ポインター、データの移動、あるいは私たちが使い慣れた他のいかなるテクニックも使わずにこれが処理できる方法の例です。

    Dcl-F  S36File  Disk(40); // プログラム記述の入力ファイル
    Dcl-Ds RecordLayout  Qualified;
 
       // Common fields                                                    
       CustNo    char(5);                                
       RecordId  char(1);                                
       OrderNumber  zoned(5);                            

       // Layout for header                              
       Dcl-Ds  Header;                                   
          OrderDate    date(*USA);                       
          ItemCount    int(5);                           
          OrderTotal   packed(7:2);                      
       End-Ds;                                           

       // Layout for Detail                              
       Dcl-Ds  Detail  SamePos(Header); <<=== ここに注目
          ItemCode     char(7);                          
          ItemQty      packed(5);                        
          ItemPrice    packed(7:3);                      
       End-Ds;                                           
    End-Ds; 
    Read S36File  RecordLayout;  // Load record into DS

見ての通り、見出し情報のレイアウトを、共通フィールドに続く入れ子になったDSとして定義しています。次に明細情報を見出し情報と同じ位置(SAMEPOS)から始まるDSとして定義しています。例えば、合計レコードがあったとしたら、同じ様に定義することになります。これはRPGで見られるこうした状況に対処する極めて巧みな方法です。
この新しいキーワードの使い道は他に沢山あります。例えば、バラバラの日、月、年のフィールドを1つの日付フィールドにまとめることができます。きっと皆さん独自の使い方を見つけられると思います。

PSDSの追加フィールド

PSDSに 2つのフィールドが追加されました。1つ目は16バイトの内部ジョブIDです。これは380桁目から395桁目にあります。以前は、この情報を得るにはQUSRJOBIというAPIを呼び出す必要がありましたので、これはずっと簡単なアプローチです。2つ目の新しいフィールドは「これが常にそこになかったなんて信じられない」という類のものです。それは8文字のシステム名です。これは、396桁目から403桁目にあります。小さな機能拡張ですが、それでも役に立ちます。

これで新しいRPGの機能についての最初のレビューは終わりです。後続の記事で動的配列のサポートのもっと深遠な側面、特に新しいスタイルの配列をパラメーターとして使用する方法について論じる予定です。

(訳注)
「可変次元配列」という言葉はvarying-dimension arrayの訳ですが、この場合の次元(dimension)はRPGの場合Dimキーワードで指定される配列サイズを意味しており、他のプログラミング言語(例えばCやJavaなど)で使われる次元とは異なります。RPGで使用できる配列は依然として1次元配列であり、IBM i 7.4での機能拡張は、予め定義した上限値の範囲内で配列サイズを実行時に変えることができるというものであることに注意してください。
この記事の筆者であるジョン・パリスもこの点を気にしてか、所々で「可変次元配列」の代りに「動的配列」(dynamic array)という呼称を使っています。

Copyright © IGUAZU