Forums >> Programming >> Proof of Concept (POC) >>
Recursion and RPG




Posted:
bvstone

Recursion and RPG

 
Recursion and RPG

This site uses recursion, mainly, to display message threads. 

Think of the message thread structure as the roots of a tree.  Each thread can have unlimited replies, and each reply can have unlimited number of replies.  To parse them, we need to start at the top, and work our way down each root to the end, then back up to each branch and down again, until we are done.

Hopefully this image will help.  

In this example a is our starting message in the thread.  Messages b, c, and d are direct replies to message a.  Messages e and f are direct replies to message b and so on and so forth.  So we need to be able to traverse this "tree" in a method where we don't rely on a set number of "children" each post will have.  

The use of recursion is a perfect fit for this type of processing.  A simplified version of the code used for this is shown below:

      *//////////////////////////////////////////////////////////////*
      * #gbf_displayThreadMessages                                   *
      *//////////////////////////////////////////////////////////////*
     P #gbf_displayThreadMessages...
     P                 B                   EXPORT
      *--------------------------------------------------------------*
     D #gbf_displayThreadMessages...
     D                 PI
     D  in_ThreadID                  64    Value
      *
     D l_level         S             10i 0 STATIC
     D l_lastThreadID  S                   LIKE(THREADID)
     D l_lastReplyID   S                   LIKE(REPLYID)
      *--------------------------------------------------------------*
      /free

       l_level += 1;

       if (l_level = 1);
         OPEN THREAD2;
       endif;

       exec SQL
         select
           PATH into :PATH from THREADPF
         where
           ACTIVE = 'Y' and THREADID = :In_ThreadID;

       #writeTemplate(PATH);

       // display any replies
       SETLL in_ThreadID THREAD2;
       READE in_ThreadID THREAD2;

       dow (not %eof(THREAD2));
         l_lastThreadID = THREADID;
         l_lastReplyID = REPLYID;
         #gbf_displayThreadMessages(THREADID);
         SETGT ThreadKey THREAD2;
         READE in_ThreadID THREAD2;
       enddo;

       if (l_level = 1);
         CLOSE THREAD2;
       endif;

       l_level -=1 ;

      /end-free
     C     ThreadKey     KLIST
     C                   KFLD                    l_lastReplyID
     C                   KFLD                    l_lastThreadID
      *--------------------------------------------------------------*
     P #gbf_displayThreadMessages...
     P                 E

The main processing goes something like this:

  1. Get the PATH to the IFS file that contains the post for the Thread.
  2. Display that post.
  3. Read through the THREAD file by the REPLY ID to find replies for this particular message.  If you find any, go to step 1 with the new Thread ID

This process then repeats for each reply to each message, and each reply to that message, which can continue forever, depending on the thread itself.  Again, think of the roots of a tree to visualize when an entire thread may look like.

STATIC Variables
The first point to make is the use of the STATIC keyword when we are defining the l_level variable.  This keyword means that the value of this variable will be available through each recursive call level of the subprocedure.  This way we know how "deep" we are into the thread as well as the replies to the particular thread.

We need to know what "level" we are at so we can open and close our file at the right time.  It's also used for other things (such as indenting replies so they appear in a "thread" format).

Because of this, the first thing we do is increment our level variables, and the last thing we do is decrement it.  

Variables that are not declared as STATIC will be reset for each recursive call level we are at.  

Traditional I/O vs SQL
The second point is that you see we're using traditional I/O (READxx, SETxx, etc) in part of this instead of SQL.  That's because cursors used for SQL (at least on the OS version we are on) are also static.  So if we open a cursor then recursively call the subprocedure again, it will try to open the same cursor, which is already open, and throw an error.

File pointers are also "static".  This is why in our code we need to reset the pointer when control is returned to the base call level.  You'll see to accomplish this we are storing the values of the last key values in the file before making the recursive call.  When control is returned we use the SETGT operation to re-position the file pointer to where we left off. 

Recursion is one of the most powerful tools that I've ever run across, especially for it's "simplicity".  It's how programs are written to play chess, parse a bill of materials file, as well as other things.  Simple, yet powerful, and when you find the perfect use for it, you'll know it. 


Last edited 09/08/2014 at 11:34:00


Reply




Copyright 1983-2017 BVSTools
GreenBoard(v3) Powered by the eRPG SDK, MAILTOOL Plus!, GreenTools for Google Apps, jQuery, jQuery UI, BlockUI, CKEditor and running on the IBM i (AKA AS/400, iSeries, System i).