Jump to content
Mike Torrettinni

Manage overloaded IfThen functions

Recommended Posts

 

22 hours ago, Mike Torrettinni said:

E2251 Ambiguous overloaded call to 'IfThen' System.StrUtils.pas(499)...

Actually a setting or compiler directive 'prioritize last defined overloaded', this issue would be solved! It works with non overloaded methods, why can't it work with overloaded and directive?

Edited by Mike Torrettinni

Share this post


Link to post
27 minutes ago, Anders Melander said:

I think I'll put that on a t-shirt:

"Hope - Helping Delphi survive since 1996"

HOP: Hope oriented programming :classic_biggrin:

Share this post


Link to post
40 minutes ago, Mike Torrettinni said:

It works with non overloaded methods, why can't it work with overloaded and directive?

This is probably a mis-feature. I'd far rather such ambiguities resulted in errors unless you fully qualified the names. In fact it would be far better if units didn't have to be imported in full. If you could import classes, names paces etc. rather than having to pull an entire unit into the importing unit's namespace. 

 

Have you decided to reimplement other rtl functions, and if not why not? What is special about IfThen that you insist on reimplementing them?

Share this post


Link to post
9 minutes ago, David Heffernan said:

If you could import classes, names paces etc. rather than having to pull an entire unit into the importing unit's namespace. 

Interesting, I like that idea! 🙂

10 minutes ago, David Heffernan said:

Have you decided to reimplement other rtl functions, and if not why not? What is special about IfThen that you insist on reimplementing them?

I guess I'm chasing something like this - then we can avoid overloaded and the whole 'reimplementing' rtl functions:

2 hours ago, Clément said:

iif<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;

Share this post


Link to post
5 minutes ago, Mike Torrettinni said:

Interesting, I like that idea! 🙂

I guess I'm chasing something like this - then we can avoid overloaded and the whole 'reimplementing' rtl functions:

A generic IfThen is trivial to write. I know I have one in my code base. 

Share this post


Link to post
Guest

I like to use this basic way to conditional "string" resulted :

// I always use this, in-line
function fncMyIIFtoStringResulted(lCondition:boolean; lTextTrue:string = 'some text'; lTextFalse:string = 'some text'):string;
begin
	result := lTextFalse; // will be default
	if lCondition then
		result := lTextTrue;
end;
...

ShowMessage( fncMyIIFtoStringResulted( ImManOrWoman, 'man', 'woman') );

 

Edited by Guest

Share this post


Link to post
1 minute ago, emailx45 said:

I like to use this basic way to conditional "string" resulted :


// I always use this, in-line
function fncMyIIFtoStringResulted(lCondition:boolean; lTextTrue:string = 'some text'; lTextFalse:string = 'some text'):string;
begin
	result := lTextFalse;
	if lCondition then
		result := lTextTrue;
end;
...

ShowMessage( fncMyIIFtoStringResulted( ImManOrWoman, 'man', 'woman') );

 

Seems odd to replicate a function that already exists in the RTL. Also to implement it by potentially assigning the result variable twice. And the default parameters are very odd. It would never make any sense to omit these parameters. 

  • Like 2

Share this post


Link to post
Guest

i dont use many "uses" when not necessary.

for me it's good thats way.

 

3 hours ago, David Heffernan said:

It would never make any sense to omit these parameters. 

 

it's interessant show "when", "where", and "why"... else, it's a empty complaint.

 

image.thumb.png.b4fc2644f49fd4c993bca278dc44b0da.png

function IfThen(AValue: Boolean; const ATrue: string;  AFalse: string = ''): string;
begin
  if AValue then
    Result := ATrue
  else
    Result := AFalse;
end;

 

System.StrUtils.IfThen if works like this... or if exist another in all system? in my searches, dont!

 

function fncMyIIFtoStringResulted(lCondition:boolean; lTextTrue:string = 'Yes'; lTextFalse:string = 'No'):string;
ShowMessage('Do you like this? ' + fncMyIIFtoStringResulted( lItsGoodOrBad, 'yes dear friend, i like it', 'no, no, it''s so bad') );

 

ShowMessage('Do you like this? ' + fncMyIIFtoStringResulted( lItsGoodOrBad));

 

Edited by Guest

Share this post


Link to post
12 hours ago, emailx45 said:

function fncMyIIFtoStringResulted(lCondition:boolean; lTextTrue:string = 'some text'; lTextFalse:string = 'some text'):string;

This is what I was commenting on. Omitting parameters 2 and 3 makes no sense. There should not be default parameters for these functions. 

Share this post


Link to post
On 11/29/2020 at 11:24 PM, David Heffernan said:

The point is not that it doesn't behave as designed. It's that the design is poor. What is needed is a conditional operator built into the language, like in almost all other languages. 

https://en.wikipedia.org/wiki/Principle_of_least_astonishment

 

23 hours ago, Attila Kovacs said:

for sure


H := (C = 0 ? 0 :
    V = r ? (g - b) / C :
    V = g ? (b - r) / C + 2 :
            (r - g) / C + 4
    );

 

And now imagine some compiler that turns that into proper code with as little conditional jumps as possible: https://godbolt.org/z/KqhKnx

Edited by Stefan Glienke
  • Like 2

Share this post


Link to post
23 hours ago, Clément said:

Hi,

 

I'm using another construction for simple types:

 

iif<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;

 

function iif<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;

begin

     if aCond then Result := aTrue else Result := aFalse;
end; 

 

No overloads, very easy to ready....

 

 

procedure SomeProcedure;

begin

     a := iif<string>( aCond, 'Hello', 'World');
     b := iif<integer>( aCond, 12, 0 );
end;

 

 

As for ternary operators, it would be a nice addition. Couldn't it be written in ASM?

@Clément This is my quick setup:

 

type
  IIF = class
    class function _<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;
  end;

class function IIF._<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;
begin
  if aCond then Result := aTrue else Result := aFalse;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  i: integer;
  s: string;
  c: TVirtualStringTree;
begin
  i := IIF._<integer>(i=1, 1, 2);
  s := IIF._<string>(i=1, '1', '2');
  c := IIF._<TVirtualStringTree>(i=1, vst1, vst2);
end;

 

Do you have a recommendation on better naming?

 

  • Like 1

Share this post


Link to post

Mine looks like this

type
  Generic = class
  public
    class function IfThen<T>(Value: Boolean; const TrueResult, FalseResult: T): T; static;
  end;

I don't think the name is that great, but my motivation was to use the same naming as the RTL. How I wish we could have generic functions outside of classes or records.

 

Note that you should make this a static class method so that the class reference does not get passed.

Edited by David Heffernan
  • Like 1
  • Thanks 1

Share this post


Link to post
type
  Conditional<T> = record
  public
    class function When(const aBool: Boolean; const WhenTrue, WhenFalse: T): T; static;
  end;

{ Conditional<T> }

class function Conditional<T>.When(const aBool: Boolean; const WhenTrue, WhenFalse: T): T;
begin
  if aBool
   then Result := WhenTrue
  else Result := WhenFalse
end;

procedure Test;
var
  i: integer;
  s: string;
  c: TVirtualStringTree;
begin
  i := Conditional<integer>.When(i=1, 1, 2);
  s := Conditional<string>.When(i=1, '1', '2');
  c := Conditional<TVirtualStringTree>.When(i=1, vst1, vst2);
end;

More verbose, but easiser to read, IMO.

  • Thanks 1

Share this post


Link to post
30 minutes ago, David Heffernan said:

Note that you should make this a static class method so that the class reference does not get passed.

OK, thanks!

 

14 minutes ago, Lars Fosdal said:

More verbose, but easiser to read, IMO.

The difference is because of it's record and not a class, right? Record is <T> type while class has a method of <T> type.

Share this post


Link to post

Well., you can do the same with both class and record, i.e. put the generic type on the type declaration or the method declaration.

I try to make my code read as much like normal language as possible - simply because reading comprehension.

Share this post


Link to post

So, now we have:

 

// overloaded Ifthen
i := IfThen(cond, 1, 2);
// namespaced overloaded 
i := ProjectName.Utilities.IfThen(cond, 1, 2);
// class 
i := Generic.IfThen<integer>(cond, 1, 2);
// namespaced class
i := ProjectName.Utilities.Generic.IfThen<integer<(cond, 1, 2);
// record
i := Conditional<integer>.When(cond, 1, 2);
// namespaced record
i := ProjectName.Utilities.Conditional<integer>.When(cond, 1, 2);

 

Any more?

Share this post


Link to post
45 minutes ago, Mike Torrettinni said:

@Clément This is my quick setup:

 


type
  IIF = class
    class function _<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;
  end;

class function IIF._<T>( aCond : boolean; aTrue : T; aFalse : T ) : T;
begin
  if aCond then Result := aTrue else Result := aFalse;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  i: integer;
  s: string;
  c: TVirtualStringTree;
begin
  i := IIF._<integer>(i=1, 1, 2);
  s := IIF._<string>(i=1, '1', '2');
  c := IIF._<TVirtualStringTree>(i=1, vst1, vst2);
end;

 

Do you have a recommendation on better naming?

 

Naming is very hard... I usually like to place similar functions together and just call it "dhsUtilsxxx".
"dhs" is my company, so to avoid conflicts with other libraries.
"Utils" is just lack of creativity. Could as well be "Lib"
"xxx" : No comments

For example:
      dhsUtilsCnd = record
            function iif<T>( aCond : Boolean; aTrue : T, aFalse : T ) : T;
             function NullIf<T>( A, NullValue : T ) : boolean;
             function Coalesce<T>( const aStr : TArray<T> ) : T;
      end;

    dhsUtilsMath = record
            function max<T>( a, b ; T ) : T; overload;
            function min<T>(a, b: T ) : T ; overload;
            function max<T>(  const A : TArray<T> ) : T; overload;
            function min<T>(  const A : TArray<T> ) : T; overload;
   end;

  dhsUtilsList = record
   end;
 

And also is not always easy to place a function in the correct "utilsxxx" record.

As I'm too lazy to write :
   dhsUtilsCond.iif<string>( SomeCondition, Expression, Expression );

It's quite readable if used as:
var
   _ : dhsUtilsCond;
begin
    _.iff<string>( ThereYouGo, 'Hello', 'World');

end;

 


 

Share this post


Link to post
27 minutes ago, Mike Torrettinni said:

The difference is because of it's record and not a class, right? Record is <T> type while class has a method of <T> type.

No. For static class methods it makes no difference whether its a class or a record. 

Share this post


Link to post

Come to think of it, you could rename Condition<T> to Use<T> to shorten the code a tad.

i := Use<integer>.When(cond, 1, 2);

Edit: There are good arguments for putting <T> on the method instead of the type, since it allows you to put overloaded methods under the same umbrella as the generic methods, but it doesn't read quite as nice.

Then again, you can have both
 

type
  Use = record
  end;
  Use<T> = record
  end;

 

  • Like 1

Share this post


Link to post

I just remembered a major reason to put the generic type on the method: Type inference.
 

type
  Use = record
  public
    class function When<T>(const aBool: Boolean; const WhenTrue: T; const WhenFalse: T): T; static;
  end;

{ Use }

class function Use.When<T>(const aBool: Boolean; const WhenTrue, WhenFalse: T): T;
begin
  if aBool
   then Result := WhenTrue
  else Result := WhenFalse
end;

procedure Test(Cond: Boolean);
type
  TObjectClass = class of TObject;
var
  i: Integer;
  b: Byte;
  c: Cardinal;
  w: Word;
  s: String;
  d: Double;
  o: TObjectClass;
begin
  // if type inference is not able to understand your code, you get
  // [dcc32 Error]: E2532 Couldn't infer generic type argument from different argument types for method 'When'
  s := Use.When(Cond,'True', 'False'); // Type inference makes the <T> argument to When optional when the parameters are the same type 
  s := Use.When(Cond, 1, 2).ToString;
  i := Use.When(Cond, 1, 2);
  b := Use.When<Byte>(Cond, 1, 2); // Needs <Byte> or you get the inference error - not sure why
  c := Use.When(Cond, 1, 2);
  w := Use.When(Cond, 1, 2);
  d := Use.When(Cond, 3.14, 42.0); // 42 without decimals gives the inference error unless you specify When<Double>
  o := Use.When<TObjectClass>(Cond, TObject, TStringList);
end;

 

  • Like 3

Share this post


Link to post
22 minutes ago, Lars Fosdal said:

Q: Is there a point to slapping Inline; on after Static; ?

That depends. I prefer to let the compiler choose whether or jot to inline. That seems to have better results than me doing it manually. 

Share this post


Link to post

Makes sense, I suppose.
Personally, I am not enthusiast enough to rummage through the variations the resulting assembly code for everything I do - so I ask.

Share this post


Link to post
57 minutes ago, Lars Fosdal said:

Makes sense, I suppose.
Personally, I am not enthusiast enough to rummage through the variations the resulting assembly code for everything I do - so I ask.

I'm not sure what looking through assembly would tell you. If performance was your motivation then you'd time the code in a realistic setting, and if it was a hotspot then you'd do something about it. 

  • Like 1

Share this post


Link to post
1 hour ago, Lars Fosdal said:

// Type inference makes the <T> argument to When optional when the parameters are the same type

Nice! Didn't know that one.

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

×