Persistent Procedure Singletons

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: