Achieving a "Closure" in Delphi 2006

104 views
Skip to first unread message

Kevin Toppenberg

unread,
May 24, 2023, 5:46:53 PM5/24/23
to Hardhats
Hey all, I thought I would share an issue I solved in CPRS and share a technique.

In CPRS, when the Encounter dialog is launched, it is shown in **modal** mode, meaning that the rest of the application is not available until after the dialog is closed.  For a few reasons, this is not an ideal user experience.    This encounter dialog allows the user to specify associated diagnosis codes and procedure codes, health factors etc to a visit.  Sometimes, to pick a correct code, I need to go back and reference my note, or look at other parts of CPRS.  And when the form is modal, it is a hassle to close it out, and then look and then relaunch.  So what needs to be changed to allow the dialog to be **non-modal**?  I'll get into this, but first discuss closures.

In Javascript (which I have used just a bit), when an anonymous function is created, the reference to the function captures not only the location of the code, but also a copy of the variable table that exists at the time of creation.  For example, one might see code like this (copied from https://javascript.info/callbacks):

//Some code here.  'A'
loadScript('/my/script.js', function(script) {
  //Some code here.  'B'
  loadScript('/my/script2.js', function(script) {
    //Some code here.  'C'
    loadScript('/my/script3.js', function(script) {
      //Some code here.  'D'
      // ...continue after all scripts are loaded
    });
  });
});
//More action here  'E'

Code like this drives me crazy when I look at javascript code.  Because my feeble mind likes to think about how code operates in sequence.  I.e. A, then B, then C, etc
But javascript seems to be written to handle the uncertainty of the internet, and allows things to happen in a sequence that can't be known at design time.    So in the code above, what really happens is that code 'A' is executed, and then code 'E' and on to any other part of the program.  Sometime later (or perhaps not at all), if the loadScript function succeeds, THEN B is executed.  And likewise sometime later C and then D will be executed. 

I don't like this because the user can continue to interact with the program, possibly steering it in a different direction, while these latent tasks are still hovering in the ether.  A real life example of this is the Veridigm program we use for pharmacy web prescribing.  If I change a user's desired pharmacy, it sends this request off to the server, and often there is a delay before this change is echoed back to the display part.  So if I go forward with sending in a script for a patient, it may be that the old pharmacy name is still displayed by their name.  And I wonder, is my script going to go to that old location, or the new one?  I can press 'SEND' at any time, but I usually wait until everything is in sync.  I don't know if that is necessary or not.  The state of the program seems indeterminate.

Back to CPRS.  I solve the above problem by disallowing certain actions (for example selecting a new patient) while I have the encounter dialog displaying in non-modal format.    But I'll illustrate the initial situation in standard CPRS

Procedure CodeBlockA()
var  a, b : integer
begin
  //Some init code
  a := 5;
  b := CodeBlockB(a);
  //Some followup code
end;

function CodeBlockB(input : integer) : integer
var  a, b : integer
begin
  //Some init code
  a := 1;
  b := CodeBlockB(a)
  //Some followup code
  Result := b*2;
end;

funtion CodeBlockC(input : integer) : integer
var  a, b : integer
begin
  //Some init code
  DialogForm.ShowModal;   //<---- wait for user to close dialog
  Result := DialogForm.Result + 8;
  //Some followup code
end;


Essentially we have CodeBockA -> CodeBlockB -> CodeBlockC -> show dialog modally

And after the dialog is closed, we Finish CodeBlockC, then finish CodeBlockB, then finish codeBlockA.

And again, the user is not allowed to do anything else in CPRS until the dialog is closed.   The first step I took to solving this was to split each function into two parts, with the send part being a callback.  So like this. 

Procedure CodeBlockA()
var  a, b : integer
begin
  //Some init code
  a := 5;
  CodeBlockB(a);
end;

Procedure HandleCodeBlockACallback;
begin
  //Some followup code for CodeBlockA
end;

function CodeBlockB(input : integer) : integer
var  a, b : integer
begin
  //Some init code
  a := 1;
  CodeBlockB(a)
end;

Procedure HandleCodeBlockBCallback;
begin
  //Some followup code for CodeBlockB
end;

funtion CodeBlockC(input : integer) : integer
var  a, b : integer
begin
  //Some init code
  DialogForm.Show;   //<-- does NOT wait for user to close dialog
end;

Well, this is a start, but it doesn't show how the callbacks get called or how the callback functions have access to needed variables.   So I next used a TStringList as the data storage for my "closure."  A TStringList is essentially a list of strings, imaging a shopping list, with each line containing one item like 'Milk' or 'bread'.  And each line is allowed to also store a corresponding number, which is usually a pointer, but it can be typecast to an integer etc.   I actually extended the TStringList object so that it has some helper functions that should be self explanatory, and I'll call this a TCallbackDataList

var
  CallbackData : TCallbackDataList;  <-- instantiated elsewhere

Procedure CodeBlockA()
var  a, b : integer
begin
  //Some init code
  a := 5;
  b := 2;
  //PUSH DATA AND CALLBACKS INTO 'STACK', I.E. PSEUDO-CLOSURE
  CallbackData.AddInt('CodeBlockA.b', b)
  CallbackData.AddProc('CALLER=CodeBlockA', @HandleCodeBlockACallback);

  CodeBlockB(a, CallbackData);
end;

function CodeBlockB(input : integer; CallbackData : TCallbackDataList) : integer
var  a, b : integer
begin
  //Some init code
  a := 1;
  b := -12
  //PUSH DATA AND CALLBACKS INTO 'STACK', I.E. PSEUDO-CLOSURE
  CallbackData.AddInt('CodeBlockB.b', b)
  CallbackData.AddProc('CALLER=CodeBlockB', @HandleCodeBlockBCallback);

  CodeBlockB(a, CallbackData);
end;

funtion CodeBlockC(input : integer; CallbackData : TCallbackDataList) : integer
var  a, b : integer
begin
  //Some init code
  DialogForm.CallbackData := CallbackData;
  DialogForm.OnClose := HandleDialogClose;
  DialogForm.Show;   //<-- does NOT wait for user to close dialog
end;

Procedure TDialogForm.HandleDialogClose(Sender : TObject);
begin
  if not assigned(Self.CallbackData) then exit;
  CallbackData.PopThenCallProc(); <-- call last proc on stack
  //above will effect calling HandleCodeBlockBCallback()
end;

//------------------------------------------
Procedure HandleCodeBlockBCallback(CallbackData : TCallbackDataList);
var b : integer;
begin
  //Some followup code for CodeBlockB
  b := CallbackData.ExtractAndDeleteInt('CodeBlockB.b')
  //Some followup code for CodeBlockA
  //NOTE that we have access to variables set up in CodeBlockA
  //so b = -12
  CallbackData.PopThenCallProc();
  //above will effect calling HandleCodeBlockACallback()
end;

Procedure HandleCodeBlockACallback(CallbackData : TCallbackDataList);
var b : integer;
begin
  b := CallbackData.ExtractAndDeleteInt('CodeBlockA.b')
  //Some followup code for CodeBlockA
  //NOTE that we have access to variables set up in CodeBlockA
  //so b = 2
end;



So this is a rather complicated way of achieving what I wanted.  Later versions of Delphi support native closures, and I can't wait to someday get TMGCPRS ported up to a newer version.  .... Someday. 

Here is the final sequence of events.
  1. CodeBlockA
  2. CodeBlockB
  3. CodeBlockC
  4. DialogForm.Show  (non-modal)
  5. Return to rest of program allowing user to do whatever they want.  Certain parts of program are disabled while DialogForm is active
  6. When DialogForm is closed --> HandleDialogClose handler called
  7. CallbackData.PopThenCallProc calls HandleCodeBlockBCallback
  8. CallbackData.PopThenCallProc calls HandleCodeBlockACallback
  9.  (some additional cleanup stuff called to ensure DialogForm is closed etc)
Anyway, it works.  I'm sure there is an easier way and I will hit my head at making this overly complicated.  But you have to start somewhere!

Thanks for reading
Kevin

rtweed

unread,
May 26, 2023, 7:29:05 AM5/26/23
to Hardhats
Kevin

The behaviour of JavaScript is due to a combination of all your code running in a single thread and being "non-blocking", so it doesn't wait for resources to be fetched: instead allowing you to define a "callback" function that will be queued and invoked once the resource has been fetched.  This behaviour is known as asynchronous logic.  

M developers are used to synchronous logic which is made possible by it being a multithreaded run-time, so asynchronous logic using callbacks can indeed be something of a black art.

JavaScript closures can be used with any function declared in-line, and are a very powerful way of locking values inside the function in a way that they can't be tampered with.

That having been said, most JS developers now tend to use what's known as async/await for their asynchronous logic rather than using callbacks.  Async/await logic allows you to write code that looks more like you'd expect in a synchronous coding language - eg your example would look more like this:

loadScript would first need to be defined as an async function.  Then it can be handled like so:

let results1 = await loadScript('xxx.js');
let results2 = await loadScript ('yyy.js');
let results3 = await loadScript('zzz.js');
// more action here

The "more action here" logic now won't execute until the three loadScripts have been executed in strict sequence, each waiting for the previous one to complete.

If there's no dependency between the three loadScripts - eg the loading of yyy.js doesn't depend on xxx.js being loaded, then you can load them in parallel using Promise.all and the loadScripts specified in an array, so they don't hold each other up when being loaded:

let res = await Promise.all([loadScript('xxx.js'), loadScript('yyy.js'), loadScript('zzz.js')]);
//more action here

Once again, the "more action here" logic won't fire until the three loadScripts have completed

Rob

Coty Embry

unread,
May 26, 2023, 3:41:07 PM5/26/23
to hard...@googlegroups.com
Yeah the “callback hell” you show can be remedied by the async await example that was given as a reply to your email. It’s much easier to read.

I like how we can do something In javascript asynchronously and not block the UI personally but yes you need to write the code to account for that!


On May 26, 2023, at 6:29 AM, rtweed <rob....@gmail.com> wrote:

Kevin
--
--
http://groups.google.com/group/Hardhats
To unsubscribe, send email to Hardhats+u...@googlegroups.com

---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hardhats/21e9bf4a-bae1-4f64-9732-1bba5647dce9n%40googlegroups.com.

David Whitten

unread,
May 26, 2023, 4:54:44 PM5/26/23
to hard...@googlegroups.com
Kevin,
I thought you were talking about closures in Delphi. 
Were you actually talking about Delphi ?

Dave
To unsubscribe, send email to Hardhats+unsubscribe@googlegroups.com


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+unsubscribe@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hardhats/471531C6-C5B2-4169-BF2D-18CD26CCE566%40gmail.com.

Kevin Toppenberg

unread,
May 26, 2023, 6:42:22 PM5/26/23
to Hardhats
Thanks for the replies all.

Dave, Yes, I was talking about how I achieve a pseudo-closure in Delphi, and the conversation took a turn to discussion how this is handled in Javascript.    I think newer versions of Delphi handle closures natively.

Best wishes,

Kevin

To unsubscribe, send email to Hardhats+u...@googlegroups.com


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.

David Whitten

unread,
Jun 1, 2023, 7:07:55 PM6/1/23
to hard...@googlegroups.com
Do you know how closures are expressed in current versions of Delphi ?

Dave
To unsubscribe, send email to Hardhats+unsubscribe@googlegroups.com


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+unsubscribe@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+unsubscribe@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hardhats/d0e93e10-6cb1-4588-a9dc-bc6b0befbc6cn%40googlegroups.com.
Message has been deleted

Sam Habiel

unread,
Jun 2, 2023, 7:42:04 AM6/2/23
to hard...@googlegroups.com
There was a message (sent yesterday) on this thread that contained copyrighted material. I went ahead and removed it. Apologies to the sender.

--Sam

To unsubscribe, send email to Hardhats+u...@googlegroups.com


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/hardhats/CAH8N84wBi1AXhg3aULs3kLGF-Y%3Dykt%3DGcvxx1rbTorasXBXE6w%40mail.gmail.com.

Kevin Toppenberg

unread,
Jun 10, 2023, 1:58:29 PM6/10/23
to Hardhats
Here is a link that discussed Closures in Delphil.


Kevin

To unsubscribe, send email to Hardhats+u...@googlegroups.com


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.


---
You received this message because you are subscribed to the Google Groups "Hardhats" group.
To unsubscribe from this group and stop receiving emails from it, send an email to hardhats+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages