I've been programming in Java and C++ for so long that when I need to write ABL (formerly 4GL), I'm sometimes left scratching my head.
Here's some simple Java, which makes use of a "static" ("class") method to validate someValue:
String validationMessage = com.joanju.Widget.validateSKU(someValue);
How would I write the equivalent in ABL?
ABL lacks static/class methods or functions. I suspect that such a feature couldn't be easily added to the compiler because of the lack of a class loader in the platform runtime, but I'm just guessing.
That leaves me with having to use Singletons. I don't need Singletons in the strict sense of the formal pattern - I just need them as method libraries.
There are plenty of ways to implement this sort of thing in ABL, but I had some very specific goals:
I ended up having to use two of my old arch enemies: an include file and a global variable. The include file was necessary to "fake" some syntactic sugar for brevity. The global variable was necessary to implement something that looks a tiny bit like a platform's class loader.
Here's my sample test case:
{com/joanju/singletonpp.i "com/joanju/widget.p" widget} {com/joanju/singletonpp.i "com/joanju/grommet.p" grommet} run testSet in widget ("Hello world!"). display dynamic-function("testGet" in widget) format "x(30)". run singletontest2.p.
The grommet.p
program is empty, and I referenced it only so that I could see that my preprocessed code wasn't getting too fat with each new reference to the include file.
The singletontest2.p
program just shows that the existing widget.p
is found and re-used:
{com/joanju/singletonpp.i "com/joanju/widget.p" widget} display dynamic-function("testGet" in widget) format "x(30)".
The include file uses an include guard to ensure that the global handle to a PP manager is defined and checked just once in each compile unit. The include contains the code for defining the handle and fetching the PP, giving us the much needed brevity in the main program code. The conditional RUN
statement will never actually run - it is there just to make sure that Callgraph and COMPILE..XREF
can do their jobs.
&if defined(com_joanju_singleton_pps) = 0 &then &global-define com_joanju_singleton_pps define new global shared variable comJoanjuSingletonPPManager as handle no-undo. if not valid-handle(comJoanjuSingletonPPManager) then run com/joanju/singletonmanager.p persistent set comJoanjuSingletonPPManager. &endif define variable {2} as handle no-undo. run getSingleton in comJoanjuSingletonPPManager ("{1}", output {2}). /* Next statement is just an xref from this program to "{1}". */ if not valid-handle({2}) then run "{1}" persistent set {2}.
Finally, the singletonmanager.p
itself is dead simple:
define temp-table singleton no-undo field progName as character field progHandle as handle index idx1 is unique progName. procedure getSingleton: define input parameter name as character no-undo. define output parameter newHandle as handle no-undo. find singleton where progName = name no-error. if available singleton and valid-handle(singleton.progHandle) then assign newHandle = singleton.progHandle. else do: run value(name) persistent set newHandle. if not available singleton then do: create singleton. assign singleton.progName = name. end. assign singleton.progHandle = newHandle. end. end procedure.
Although there are a myriad of ways to implement function libraries and find and reference them in ABL, this method satisfies my goals:
COMPILE..XREF
entry from the program to the PP.
RUN PERSISTENT SET
, the RUN IN handle
, and the DYNAMIC-FUNCTION(name IN handle)
calls.
Comments
Check out my procedure manager
It takes care of stuff like this and a whole lot more. It's currently over on the PEG utilities page, mostly because I haven't created a project here yet...
PPM
mostly because I haven't created a project here yet...
Other than the fact that you folks up there already had your Thanksgiving ... what better time to do it than Thanksgiving weekend!
Other thoughts about Singletons
For a solution to creating pseudo-singletons in OOABL, see Replacing A Session SuperProcedure With A Class In OOABL
Also, in a .p context, an interesting piece of code from the AutoEdge example is autoedge/server/oeri/support/extendprocedure.i which consists of:
where the first argument is a procedure handle and the second is the name of the procedure. This doesn't do as much management as John's code does, but it is a simple way to run one and only one copy of a super-procedure.
procedure lookup and scaling
That exact technique is why I made the comment about using an indexed lookup lookup for the handles, so that it would scale well with dozens of persistent procedures. It would be interesting to hear if anybody has ever put a stopwatch to that method vs. a lookup in a temp-table. I can't imagine that dozens of string comparisons comes without a price. It's not unusual for a RUN statement to find itself in a tight loop... :)
Performance is good
My procedure manager's running in a production application, and there's no noticeable delay when starting all the procedures (as well as figuring out their dependencies and starting new instances as required).
The big plus with the procedure manager compared to traversing a list of procedure handles is it gives the developer a lot more flexibility in terms of what kind of persistent objects one creates, as well as taking away all the management / cleanup problems.
Relative Performance
I don't see the issue as being performance as much as it is the level of management. Your approach or any other persistent procedure manager seeks to provide a conscious level of management to the PPs instantiated in a session. The simple code snippet illustrates a way to not use a manager, but to still instantiate only a single instance per session. This is not something that one would have within a tight loop because it is start of program code to obtain a handle. Thence forth, one does runs in handle and that would be the run which might appear in the tight loop.
Of course, I wouldn't use either one any more ... I'd use the OO version!
RUN in tight loop
I think you misunderstood. Yes program A would obtain the handle just once at its start. It's the
RUN A.p
statement(s) that I'm concerned about. Or the calls which lead to those calls...When?
If one is running this code only once at initialization, I don't see how the performance difference is relevant.