Fluent coding - debugging

I don’t know if this is old and/or obvious, but I’ve read a bit about fluent-style Delphi over the years, and people have mentioned debugging in the IDE as one issue.

Having been getting a bit of a functional programming perspective this year, I had an idea about how it might work to apply the identity transformation … ie a ‘function’ (or whatever) that returns you exactly what you give it.

I didn’t have time tonight to look at these:

" Fluent interfaces are used in e.g. TStringBuilder, THTMLWriter and TGpFluentXMLBuilder."

So I dug up some old code that was offered in a previous discussion (2011 !).

I stuck the code here: GitHub - pmcgee69/Fluent-ID-example: Example of debugging fluent style code in Delphi using the identity transformation.

and captured a 2 min video of using it to step through code :

The ultimate purpose was just processing ideas - ie using an “id” function (or you could call it “brk”) and a single break point as a way to place a debug point or points into a fluent sequence.

With that sort of code I find myself repeatedly doing a cycle of F7 followed by Shift+F8 to reach the point I actually want to debug.

Or I use some local variables temporarily so I don’t go insane trying to keep track of which part of the fluent call I’m executing.

Debugging fluent api’s is not fun in any language - but for the most part you just use them and let the library authors debug them :wink: It’s not a pattern that I would recommend you adopt for every day line of business code.

Delphi Mocks uses a fluent interface, but in that situation the type each call returns is not always the same - doing this

//SETUP: Add a check that SimpleMethod is called atleast once.
  mock.Setup.Expect.AtLeastOnce.When.SimpleMethod;

in a non fluent manner would require a bunch of variables and make the code extremely verbose. So in this scenario it makes sense.

1 Like

Seeing whether it was possible at all was a first goal.
I need to look into how it might interact with pre-existing classes / interfaces.

My next goal is to see how extra info (like a fail / quit condition) can also be passed along the sequence, and what steps can be taken to short-circuit the sequence if that occurs.

Any info on existing approaches? I will go look at the code from gabr + vincent + others.

The fluent paradigm requires that a call returns a fixed type - my guess would be if it fails the only way to let the use know would be to raise an exception.

1 Like

Having a fixed type that contains possible failure info doesn’t conflict with the idea of a “Maybe” datatype that can encompass both (X and Nothing).

That’s something I have in mind, but I certainly don’t know enough yet to say if it could become something workable.

It might eg work when you are the one writing the classes / objects / interfaces. Eg the TFluentObj and TFluentIntf from my example could carry a boolean property, I think.

But I assume you might meet a problem when you want to use a ToString or whatever from other code in the sequence. :thinking:

From a design perspective, any failure should generate an exception. Otherwise you are “bastardising” the elegant design :wink:

Exceptions are regarded in some circles as expensive, and there are applications where they can’t be used. This is the influence some of the c++ world is having on me.

But also, I think this comes from not traditionally having Maybe / Option / Nullable / Either types in Delphi (at least referred to in that way).

We are just talking about composing functions with identical signatures (monoids and monads) - and other languages are able to short circuit within that framework, so it’s something I’m trying to think about.

In delphi it’s more the try/except that is expensive.

The issue is how do you check for an error in a call chain

One.Two.Three.Four

if Two fails… what should happen?

1 Like

I think trying to shoehorn functional programming into delphi will be difficult, just as it is in c# (although easier). If you look at functional languages, they all have features explicitly designed to enable functional programming.

I’ve tried to bend delphi in many weird and wonderfull ways just to try and port c# - with some successes but mostly failures (often due to poor generics implementation).

1 Like

I’m interested to find out more about what happens in Delphi when exceptions occur.

Back in April (2021), prompted from both the c++ direction, and learning about Maybe types, I ran some simple measurements with division - either avoiding /0 or throwing, just to get an idea of the impact.

https:// github.com/ pmcgee69/Exceptions-Cost-in-Delphi

I’m sure it’s pretty rough, but I put this together as a very simple idea of how it might work.
I’m sure it could be done much more elegantly.
(At a much higher level, I’ve been having a look at Primož 's Pipelines)

   var _is : Internetstring;

   if not _is.clear(Fail)
             .get_res_name('google.com')
             .get_res_type('source')
             .get_res_next('cdn')
             .print
   then  err('data 1 retrieval error');                //  aaa.bbb.ccc

   if not _is.clear(Fail)
             .get_res_name('apple.com')
             .get_res_type('')
             .get_res_next('cdn')
             .print
   then  err('data 2 retrieval error');                //  data 2 retrieval error

          _is.clear(Succeed)
             .get_res_name('hp.com')
             .get_res_type('')
             .get_res_next('cdn')
             .print;                                   // aaa.ccc

Our standard language is, roughly speaking, functions and data types.
I think there are going to be Functional ideas that might change how we understand those things and influence how we use them - as opposed to changing the language per se.

My weekend … making an experimental mess, and slowly climbing out / cleaning up. :slight_smile:

To do some performance testing, I changed my example code to generate 10m strings of length = 8, drawn from base-21 characters. Then methods to trim the string to length = 4, and try to interpret that as hex, and save the result.

I wanted to try out TVirtualMethodInterceptor … which requires virtual methods. I had started with records, so measured perf, changed to classes, measured again; changed to virtual methods, measured again; and then started experimenting with the TVMI.

Creating, using, and destroying, VMI x 10m slowed things down TWENTY times.
But using a single VMI over and over cost basically nothing.

Trying exceptions to short-circuit was FIFTY times slower than using an error flag.
Next aim is to use the VMI OnBefore method to do the error check so it doesn’t have to be in each of the class methods.

(I also finally realised how to define constant records)

//Unit vector constants
const
  iHat : TVector3D = (x: 1; y: 0; z: 0);
  jHat : TVector3D = (x: 0; y: 1; z: 0);
  kHat : TVector3D = (x: 0; y: 0; z: 1);

1 Like
   Timer( procedure begin  for var i := 1 to arr_size do begin
                               try
                                 arr2[i] := arr[i].trunc(4).hextoint.int;
                               except
                                 arr2[i] := fail_int;
                               end;
                           end;
                    end );

   quickcheck(arr, arr2);   writeln('-');   cleararray(arr, arr2, arr_size);
   quickcheck(arr, arr2);

   var vmi := TVirtualMethodInterceptor.Create(Transformstring);
   (*
   vmi.OnBefore := procedure(  Instance  : TObject;        Method: TRttiMethod;
                               const Args: TArray<TValue>; out DoInvoke: Boolean;  out Result: TValue     )
                      begin
                        //Write('[before] ', Method.Name,'  ');
                      end;
   *)
   X := Transformstring.Create;
   vmi.Proxify( X );

   Timer( procedure
          begin  for var i := 1 to arr_size do  begin
                         X := arr[i];
                         arr2[i] := X.trunc(4).hextoint.int;
                 end;
          end );
   vmi.Free;
   quickcheck(arr, arr2);   writeln('-');

(Changing the error rate per character from 4-in-20 to 4-in-100, turns the numbers into ~100ms with error flags, ~1300ms with exceptions).

I was fooling myself. Investigating along, I had commented out the OnBefore method to simplify trying different things. But I ultimately found out that each instance of a class does need to be Proxify’d individually.
So the result is about a x20 speed loss over flags. Oh well. :disappointed_relieved: :sweat_smile:

it is a constant penalty, so could be better or worse than exceptions, depending on how often they fire.

Just in case someone looks at your results and thinks exceptions are bad and should be avoided, it must be said that if you’re relying on exceptions being raised repeatedly you’re doing it wrong.

It’s in the name, exceptions are for exceptional circumstances not for probable states.

If you’re writing code that will be called by others, you don’t necessarily know whether the raising an exception or returning a boolean will be optimal for the callers application. In those circumstances it can be a good idea to provide two versions of the same method one that returns a boolean, and one that raises an exception.

1 Like

I’d say certainly yes to this - except for example, Indy seems to have been built originally entirely around exceptions. Remy seems to have added other options maybe around 2016(?)

“Just in case someone looks at your results and thinks exceptions are bad and should be avoided”

The point of that part of the exercise, for me, was to understand how expensive exceptions are - which of course is clearly VERY - compared to “ordinary code”. I think it’s good to have that info in mind.
Also, just saying, if you google “exceptions are bad and should be avoided” … there’s plenty of discussion. The C++ community seems to be like 50/50 on it. On one hand, Bjarne Stroustrop has argued for exceptions … on the other the language has “noexcept” qualifiers because they can help the compiler with optimisation.

Certainly @Lachlan , @vincent , Remy are lightyears ahead of me regarding development … I’m just seeing where my (20 yrs inside, but also) slightly outside perspective leads to.

It feels to me that TryStrToInt, with an out parameter, is an old-fashioned function trying to return a Maybe type.

On the fluent idea, reading around, I see that the idea of expanding the return space to something that is your desired return type OR error info can be expressed in other ways. A PLOP book from 1997 (plop) mentions a NullObject pattern …