Lazarus

Programming => General => Topic started by: Чебурашка on December 01, 2022, 12:09:55 pm

Title: Code shaping advice
Post by: Чебурашка on December 01, 2022, 12:09:55 pm
Hello I have this problem:

I have 2 methods (in reality it is much more, and the need comes from working with database try catches, if you like I can show the original stuff):

Code: Pascal  [Select][+][-]
  1. procedure MethodA(2 parameters);
  2. begin
  3.   // body of A using the 2 params
  4. end;
  5.  
  6. procedure MethodB(10 parameters);
  7. begin
  8.   // body of B using the 10 params
  9. end;
  10.  

These methods can raise exceptions. And the management of these exceptions is always the same.

Therefore I decide to wrap them in a ExcHandlingWrapped version:

Code: Pascal  [Select][+][-]
  1. procedure ExcHandlingMethodA(N extra parameters necessary for the external part + 2 parameters from the real MethodA);
  2. begin
  3.   // do things before (always the same regardless of MethodA) using the N params
  4.   MethodA(his 2 parameters);
  5.   // do things after and manage exceptions (always the same regardless of MethodA) using the N params
  6. end;
  7.  
  8. procedure ExcHandlingMethodB(N extra parameters necessary for the external part + 10 parameters from the real MethodB);
  9. begin
  10.   // do things before (always the same regardless of MethodB) using the N params
  11.   MethodB(his 10 parameters);
  12.   // do things after and manage exceptions (always the same regardless of MethodB) using the N params
  13. end;
  14.  

I was wondering if is possible directly in the language or someone can suggest me a trick to do something:

Code: Pascal  [Select][+][-]
  1. procedure ExcHandlingUniversal(N extra parameters necessary for the external part, parameterless proc);
  2. begin
  3.   // do things before (always the same regardless of proc) using the N params
  4.   proc();
  5.   // do things after and manage exceptions (always the same regardless of proc) using the N params
  6. end;
  7.  

so that I can do something like:

Code: C#  [Select][+][-]
  1. ExcHandlingUniversal(N parameters, () ->
  2. {
  3.   MethodA(his 2 parameters);
  4. });
  5.  
  6. ExcHandlingUniversal(N parameters, () ->
  7. {
  8.   MethodB(his 10 parameters);
  9. });
  10.  

Sorry for the syntax is somehow mixed with C#, but I hope you understand what I mean.

If I think to pascal I should do something like

Code: Pascal  [Select][+][-]
  1. var
  2.  MethodAP1: type;
  3.  MethodAP2: type;
  4.  
  5. procedure MethodACaller();
  6. begin
  7.   MethodA(MethodAP1, MethodAP2);
  8. end;
  9.  
  10. var
  11.  MethodBP1: type;
  12.  MethodBP2: type;
  13.  // up to 10
  14.  
  15. procedure MethodBCaller();
  16. begin
  17.   MethodB(MethodBP1, MethodBP2, up to 10);
  18. end;
  19.  


and then do something like

Code: Pascal  [Select][+][-]
  1.  
  2.  MethodAP1 := value;
  3.  MethodAP2 := value;
  4.  
  5. ExcHandlingUniversal(N parameters, @MethodACaller);
  6.  
  7.  MethodBP1 := value;
  8.  MethodBP2 := value;
  9.  // up to 10
  10.  
  11. ExcHandlingUniversal(N parameters, @MethodBCaller);
  12.  

Do I don't like so much because it might open problems in case of concurrent threads due to the external variables. And is kinda ugly.

Some idea?
Thank you
Title: Re: Code shaping advice
Post by: beria on December 01, 2022, 12:31:53 pm
Maybe I mean something else, but in such cases I always use large static (more often) or dynamic arrays of procedural variables, filled with addresses of all procedures of the same type. And accordingly, I make a common procedure with all the handlings and exceptions, for the whole array....
And yes, shared variables for all threads work through defining them in the "threadvar" section

Title: Re: Code shaping advice
Post by: Чебурашка on December 01, 2022, 12:39:43 pm
Maybe I mean something else, but in such cases I always use large static (more often) or dynamic arrays of procedural variables, filled with addresses of all procedures of the same type. And accordingly, I make a common procedure with all the handlings and exceptions, for the whole array....
And yes, shared variables for all threads work through defining them in the "threadvar" section

How do you pass to each of these procedural variables the right (amount of) parameters? I was thinking also to something like this, but I found complicated to pass the variables, unless one creates a object containing the right (amount of) parameters for each single procedural variable.

But thank you anyway for sharing the idea.
Title: Re: Code shaping advice
Post by: Zvoni on December 01, 2022, 12:55:17 pm
What about open array of const as argument?
Title: Re: Code shaping advice
Post by: jamie on December 01, 2022, 01:07:55 pm
Open arrays are fine but then it needs to be structured in such a way that it can determine what data is what without hacking it.

 How about using a record that contains information and pass that?
Title: Re: Code shaping advice
Post by: Wallaby on December 01, 2022, 01:10:18 pm
I can think of two ways of doing it.

One is using 'array of const' which is basically for any number of parameters, but you will need to access them by index, which may be hard to understand the code:

Code: Pascal  [Select][+][-]
  1. procedure MethodA(const Params: array of const);
  2. begin
  3.   //do stuff
  4. end;
  5.  
  6. procedure MethodB(const Params: array of const);
  7. begin
  8.   //do stuff
  9. end;
  10.  
  11. procedure CallMyMethod(const Params: array of const; const MyMethod: TMyMethod);
  12. begin
  13.   try
  14.     //do some common stuff
  15.     MyMethod(Params);
  16.   finally
  17.     //and more common stuff here
  18.   end;
  19. end;
  20.  
  21. procedure TForm1.FormCreate(Sender: TObject);
  22. begin
  23.   CallMyMethod(['aaa', 'bbb'], MethodA);
  24.   CallMyMethod([1, 2, 3], MethodB);
  25. end;  

Another one is making parameters fields of an object with a common class, e.g.

Code: Pascal  [Select][+][-]
  1.   TBaseParams = class(TObject);
  2.  
  3.   TParamsForA = class(TBaseParams)
  4.     Param1: string;
  5.     Param2: string;
  6.   end;
  7.   TParamsForB = class(TBaseParams)
  8.     Param1: double;
  9.     Param2: double;
  10.     Param10: double;
  11.   end;
  12.  
  13.   TMyMethod = procedure(const Params: TBaseParams);
  14.  
  15.  
  16. implementation
  17.  
  18. procedure MethodA(const Params: TBaseParams);
  19. var
  20.   MyParams: TParamsForA;
  21. begin
  22.   MyParams := Params as TParamsForA;
  23.   //do stuff
  24. end;
  25.  
  26. procedure MethodB(const Params: TBaseParams);
  27. var
  28.   MyParams: TParamsForB;
  29. begin
  30.   MyParams := Params as TParamsForB;
  31.   //do stuff
  32. end;
  33.  
  34. procedure CallMyMethod(const Params: TBaseParams; const MyMethod: TMyMethod);
  35. begin
  36.   try
  37.     //do some common stuff
  38.     MyMethod(Params);
  39.   finally
  40.     //and more common stuff here
  41.   end;
  42. end;
  43.  
  44. procedure TForm1.FormCreate(Sender: TObject);
  45. var
  46.   ParamsForA: TParamsForA;
  47.   ParamsForB: TParamsForB;
  48. begin
  49.   ParamsForA := TParamsForA.Create;
  50.   ParamsForA.Param1 := 'aaa';
  51.   ParamsForA.Param2 := 'bbb';
  52.   CallMyMethod(ParamsForA, MethodA);
  53.   ParamsForA.Free;
  54.  
  55.   ParamsForB := TParamsForB.Create;
  56.   ParamsForB.Param1 := 1;
  57.   ParamsForB.Param2 := 2;
  58.   ParamsForB.Param10 := 3;
  59.   CallMyMethod(ParamsForB, MethodB);
  60.   ParamsForB.Free;
  61. end;

Alternatively you could use a dictionary or similar structure to pass multiple values in a single argument.
Title: Re: Code shaping advice
Post by: PascalDragon on December 01, 2022, 09:56:05 pm
Some idea?

Starting with FPC 3.3.1 you can do the following:

Code: Pascal  [Select][+][-]
  1. program ttest;
  2.  
  3. {$mode objfpc}
  4. {$modeswitch functionreferences}
  5. {$modeswitch anonymousfunctions}
  6.  
  7. type
  8.   TProc = reference to procedure;
  9.  
  10. procedure ExceptionWrapper(aProc: TProc);
  11. begin
  12.   try
  13.     aProc();
  14.   except
  15.     // Whatever
  16.   end;
  17. end;
  18.  
  19. procedure Proc1({ arg decl });
  20. begin
  21. end;
  22.  
  23. procedure Proc2({ arg decl });
  24. begin
  25. end;
  26.  
  27. begin
  28.   ExceptionWrapper(procedure begin
  29.     Proc1({ args });
  30.   end);
  31.  
  32.   ExceptionWrapper(procedure begin
  33.     Proc2({ args });
  34.   end);
  35. end.
Title: Re: Code shaping advice
Post by: Чебурашка on December 01, 2022, 10:05:54 pm
Starting with FPC 3.3.1 you can do the following:

This is the right answer.
A syntax that transparently allows passing the parameters to the procedure that will be later injected in the middle of the wrapping method.
Title: Re: Code shaping advice
Post by: Чебурашка on December 02, 2022, 01:43:20 pm
FPC 3.3.1

I forgot the most important question then: when is this going to be released as stable?
I truly hope asap because this feature is super useful/cool.
Title: Re: Code shaping advice
Post by: Thaddy on December 02, 2022, 01:47:51 pm
When its ready....
Title: Re: Code shaping advice
Post by: Чебурашка on December 02, 2022, 01:55:30 pm
When its ready....

Good old debian style :)
Title: Re: Code shaping advice
Post by: PascalDragon on December 02, 2022, 05:19:31 pm
I forgot the most important question then: when is this going to be released as stable?

Definitely not this year anymore. Maybe middle to late next year, at the latest I'd say 2024. But as always: this is without any guarantees.
Title: Re: Code shaping advice
Post by: Чебурашка on December 03, 2022, 09:47:04 am
Definitely not this year anymore. Maybe middle to late next year, at the latest I'd say 2024. But as always: this is without any guarantees.

Glory to those who make the fpc. Now, middle of 2023 and after.
TinyPortal © 2005-2018