Jump to content
Mike Torrettinni

Boolean short-circuit with function calls

Recommended Posts

Exactly this is the way I would prefer like David says.

If you change xVal to something meaningful, you will see why:

 

Result := isDog or isCat or isHorse;

  • Thanks 1

Share this post


Link to post
17 hours ago, Mike Torrettinni said:

I can shorten this with:

 


function IsAnyTrue2: boolean;
begin
  Result := false;
  
  Result := A or Result;
  Result := B or Result;
  Result := C or Result;  
end;

With boolean short circuit you probably won't get B and C executed if A returns True. I doubt that order of execution in a condition is specified, optimizer could swap them.

Moreover, do you really intend to return true if just some of the functions return true?

My option:
 

res := A;
Result := Result or res;
...

 

  • Thanks 1

Share this post


Link to post
7 minutes ago, Fr0sT.Brutal said:

I doubt that order of execution in a condition is specified, optimizer could swap them.

Yes it is - optimizer does not reorder. If it would short-circuit evaluation would be broken.

 

FWIW setting Result to False and or'ing it with A is unnecessary - x or False = x

Edited by Stefan Glienke

Share this post


Link to post
4 minutes ago, Stefan Glienke said:

Yes it is - optimizer does not reorder. If it would short-circuit evaluation would be broken.

Okay you're right

Quote

In the {$B-} state, the compiler generates code for short-circuit Boolean expression evaluation, which means that evaluation stops as soon as the result of the entire expression becomes evident in left to right order of evaluation.

 

5 minutes ago, Stefan Glienke said:

setting Result to False and or'ing it with A is unnecessary - x or False = x 

Sure. Just a little redundant code to make all blocks similar.

Share this post


Link to post
13 minutes ago, Fr0sT.Brutal said:

With boolean short circuit you probably won't get B and C executed if A returns True. I doubt that order of execution in a condition is specified, optimizer could swap them.

Nonsense. Evaluation is always left to right. Period.

 

13 minutes ago, Fr0sT.Brutal said:

Result := Result or res;

No. That will suffer from boolean short circuit which is the whole point of this thread. Actually at this point it seems that the point of the thread has become for everyone to have a different opinion. I wonder why the simplest questions, with the simplest answer, always gets the most attention.

Edited by Anders Melander
Duh
  • Like 1
  • Haha 1

Share this post


Link to post
14 hours ago, aehimself said:

Introduce a Variant in the check. I was hitting my head for days when I finally found out that Delphi always evaluates a boolean fully, if a variant comparison is present.

Hmm, I would rather consider it as bug

Share this post


Link to post
2 minutes ago, Anders Melander said:

No. That will suffer from boolean short circuit which is the whole point of this thread. Actually at this point it seems that the point of the thread has become for everyone to have a different opinion

The whole point of this thread is to get all functions executed.

Share this post


Link to post
22 hours ago, Mike Torrettinni said:

Interesting, but I assume this is only applicable to this exact example, right? Can this be used if A,B and C have arguments?

If they all have the SAME argument types, yes.  The TFunc generic supports 0..4 arguments, plus the return type:

TFunc<TResult> = reference to function: TResult;
TFunc<T,TResult> = reference to function (Arg1: T): TResult;
TFunc<T1,T2,TResult> = reference to function (Arg1: T1; Arg2: T2): TResult;
TFunc<T1,T2,T3,TResult> = reference to function (Arg1: T1; Arg2: T2; Arg3: T3): TResult;
TFunc<T1,T2,T3,T4,TResult> = reference to function (Arg1: T1; Arg2: T2; Arg3: T3; Arg4: T4): TResult;
20 hours ago, Stefan Glienke said:

That code does not compile, method references cannot be declared as consts.

I don't have a working IDE right now, so I didn't try to compile it.  I was trying to be clever using a const, instead of a var that has to be recreated every time the function is called, eg:

function IsAnyTrue5: boolean;
var
  Funcs: array[0..2] of TFunc<Boolean>;
  i: Integer;
begin
  Result := False;
  Funcs[0] := A;
  Funcs[1] := B;
  Funcs[2] := C;
  for i := Low(Funcs) to High(Funcs) do
    Result := Funcs[i]() or Result;
end;

Or:

function IsAnyTrue5: boolean;
var
  Funcs: TArray<TFunc<Boolean>>;
  i: Integer;
begin
  Result := False;
  Funcs := [A, B, C];
  for i := Low(Funcs) to High(Funcs) do
    Result := Funcs[i]() or Result;
end;

Or:

var
  Funcs: TArray<TFunc<Boolean>>;

function IsAnyTrue5: boolean;
var
  i: Integer;
begin
  Result := False;
  for i := Low(Funcs) to High(Funcs) do
    Result := Funcs[i]() or Result;
end;
    
initialization
  Funcs := [A, B, C];

In any case, the idea being to put all of the functions into an array, and then loop through the array calling them and tallying up the results.  This way, more functions can be added in the future with minimal effort,

Quote

What you probably meant was this:


const
  Funcs: array[0..2] of function: Boolean = (A, B, C);

Sure, that will work.  I knew TFunc existed, I had just forgot about plain function pointers, too.

Quote

And yes, that only works if they are parameterless otherwise you would have to declare that differently - but again only works if they are have homogeneous signatures.

Yes, all of the functions would have to be of same signature.  Otherwise they have to be called individually.  Since the OP mentioned running through a lot of options, I assume there is some common interface for them.

Edited by Remy Lebeau

Share this post


Link to post
1 hour ago, Vandrovnik said:

Does anybody know, why is this "feature" present?

It makes no sense, especially if a compiler directive instructs otherwise. My guess is that there is some badly written structure behind the scenes and it would be too much effort to "fix" it now.

This is typically when we start to name our bugs, celebrate their birthday, call them a feature and see where they evolve to 🙂

  • Like 2
  • Haha 2

Share this post


Link to post
58 minutes ago, aehimself said:

My guess is that there is some badly written structure behind the scenes and it would be too much effort to "fix" it now.

Who says it need fixing? Do you realize how much code it would break if the behavior was changed now?

Also, maybe find out why it works the way it does before calling it a bug.

Share this post


Link to post
9 minutes ago, Anders Melander said:

Who says it need fixing? Do you realize how much code it would break if the behavior was changed now?

Yes, I phrased myself incorrectly. I am pretty damn sure it would cause havoc now, after these many years. What I meant in the beginning to fix it [...], thanks for pointing that out.

11 minutes ago, Anders Melander said:

Also, maybe find out why it works the way it does before calling it a bug.

All I know is I had an imagination on how things should work and an urgent bug to fix. Caused me a serious headache. I'm sorry if I offended anyone by calling Earl a bug 🙂

  • Haha 2

Share this post


Link to post
5 minutes ago, aehimself said:

I'm sorry if I offended anyone by calling Earl a bug

 

Priceless 🙂

 

Share this post


Link to post
2 hours ago, Vandrovnik said:

Does anybody know, why is this "feature" present?

  1. If either of the operands in a boolean expression is a variant then both operands are converted to variants. This is documented.
  2. Expressions on variants are evaluated using intrinsic functions implemented in system.variants.pas (_VarOr, _VarAnd, _VarAdd, etc. etc.).
  3. Because of the result of an expression isn't known until these functions has been called the compiler can't do short circuit evaluation on the expression.

Look at the asm generated and it will all make sense :classic_wink:

 

3 minutes ago, aehimself said:

I'm sorry if I offended anyone by calling Earl a bug 🙂

No offense taken, but don't cry wolf just because the dog meows.

  • Thanks 1

Share this post


Link to post

In general, if you have a function that takes arguments, and you supply functions to get values for those arguments, then by necessity all of the parameter functions will be evaluated before the function itself is called. This is a point of contention with people who would like to see a trinary operator added to Delphi similar to what's in C/C++ (and many other languages).

 

boolean rslt = a ? b : (c ? d : (.....) );

 

vs

 

Result := func( a, b, c, d, ..... )

 

The trinary operator will only evaluate the parameters as they are needed, while Delphi will always evaluate ALL of the functions given as parameters to func.

 

(rslt = a ? b : c) in c/c++ does not always produce the same result as rslt := IfThen( a, b, c ) in Delphi.

 

In the former, 'c' is not evaluated if 'a' <> 0 (ie., true), and 'b' is not evaluated if 'a' = 0.

 

But in the latter, a, b, and c are ALL evaluated BEFORE IfThen is called.

 

This is particularly relevant if you want to use a construct like this to test if a reference is assigned or not:

 

rslt := IfThen( Assigned(obj_ref), obj_ref.xyz, NIL )

 

will throw an exception when it attempts to evaluate the second parameter when obj_ref = NIL. That's exactly what you're trying to avoid!

 

rslt = (Assigned(obj_ref) : obj_ref.xyz ? NIL)   does what you'd expect all the time.

 

Interestingly, we have to suffer through this mess with lengthy if...then...else tests waiting for "nullable" values to be added to the language (after 25+ years) rather than stoop so low as to implement a simple construct that solves the problem very elegantly and simply, but is regarded as too offensive to "Pascal purists" who think it's sacreligious to steal such a construct from c/c++ for ANY reason!

 

Yes, we'll see nullable values added to Delphi long before a trinary operator.

 

Edited by David Schwartz
  • Like 1

Share this post


Link to post
6 hours ago, David Schwartz said:

Yes, we'll see nullable values added to Delphi long before a trinary operator.

How is that relevant?

Share this post


Link to post
On 8/23/2020 at 2:01 AM, Anders Melander said:

How is that relevant?

Nullable values and associated operators are a very long way around getting the equivalent of a trinary operator for getting a value through an object reference only if it's defined, and not having to deal with an exception otherwise.

Share this post


Link to post
On 8/23/2020 at 3:58 AM, David Schwartz said:

Interestingly, we have to suffer through this mess with lengthy if...then...else tests waiting for "nullable" values to be added to the language (after 25+ years) rather than stoop so low as to implement a simple construct that solves the problem very elegantly and simply, but is regarded as too offensive to "Pascal purists" who think it's sacreligious to steal such a construct from c/c++ for ANY reason!

Many features can be nicely added to the Pascal and fit well. 

 

https://docs.elementscompiler.com/Oxygene/Delphi/NewFeatures/

 

The people you talk about are not really Pascal purists, rather people that can't be bothered to learn something new. Delphi never suffered from purists... even its predecessor Turbo Pascal was always innovative and expanded features on top of standard Pascal. We would not have Pascal with objects otherwise. 

On 8/23/2020 at 3:58 AM, David Schwartz said:

Yes, we'll see nullable values added to Delphi long before a trinary operator.

 

 

Probably not. Ternary operator is easier to implement than whole nullable support. But like with many other compiler features, someone needs to implement all that - that is the weakest point and the core issue, not "purists" as such.

  • Like 2

Share this post


Link to post
9 hours ago, Dalija Prasnikar said:

Probably not. Ternary operator is easier to implement than whole nullable support. But like with many other compiler features, someone needs to implement all that - that is the weakest point and the core issue, not "purists" as such.

Nullables are in the works for Delphi, according to one or two published roadmaps now. Trinary operators ... nowhere to be seen. (I guess "trinary" is the correct term, not "ternary".)

Share this post


Link to post
1 hour ago, David Schwartz said:

Nullables are in the works for Delphi, according to one or two published roadmaps now. Trinary operators ... nowhere to be seen. (I guess "trinary" is the correct term, not "ternary".)

The only mention for nullables was in context of custom managed records that are prerequisite for implementing nullables.

 

From that perspective we kind of have nullable support right now in 10.4. But that is far from having full compiler support for nullable types. Nullable types include way more than just defining common RTL type (we don't even have that in 10.4) and one of the things that such support also implies (assumes) is ternary operator.  Additionally ternary operator has more broader usage scope than nullables themselves and thus is more interesting, worthwhile feature.

  • Like 2

Share this post


Link to post
5 minutes ago, Dalija Prasnikar said:

Additionally ternary operator has more broader usage scope than nullables themselves and thus is more interesting, worthwhile feature.

Indeed. From C#, the only things I'd like to have in Delphi are:

- Changing code while debugging (I know, possible because of JIT in .NET, so not going to happen but still, it's really-really useful)

- Faster and more precise error insight and code completion (although, I regularly have to use my script to delete .vs folders because it goes nuts...)

- Linq

- Ternary operators

 

I can live without everything else.

Share this post


Link to post
On 8/20/2020 at 2:02 PM, Mike Torrettinni said:

Result := A or Result;

This seems to be the winner, so far.

It doesn't need any extra setting up (Funcs arrays, temp variables, ...). I can add as many as I need, and I don't need to change any other expressions (Result := A or B ...).

The simpler Result := Eval(...) or Result = A or B... looked really good in simple examples, but as soon as you have any comments needed or additional conditions, the winner becomes a better choice.

 

Thank you, I really appreciate all suggestions!

Share this post


Link to post
1 hour ago, Mike Torrettinni said:

This seems to be the winner, so far.

It doesn't need any extra setting up (Funcs arrays, temp variables, ...). I can add as many as I need, and I don't need to change any other expressions (Result := A or B ...).

The simpler Result := Eval(...) or Result = A or B... looked really good in simple examples, but as soon as you have any comments needed or additional conditions, the winner becomes a better choice.

 

Thank you, I really appreciate all suggestions!

You still need comments for future readers. 

  • Like 2

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×