Mike Torrettinni 198 Posted November 30, 2020 (edited) 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 November 30, 2020 by Mike Torrettinni Share this post Link to post
Clément 148 Posted November 30, 2020 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 Share this post Link to post
David Heffernan 2347 Posted November 30, 2020 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
Mike Torrettinni 198 Posted November 30, 2020 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
David Heffernan 2347 Posted November 30, 2020 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 Posted November 30, 2020 (edited) 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 November 30, 2020 by Guest Share this post Link to post
David Heffernan 2347 Posted November 30, 2020 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. 2 Share this post Link to post
Guest Posted November 30, 2020 (edited) 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. 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 November 30, 2020 by Guest Share this post Link to post
David Heffernan 2347 Posted December 1, 2020 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
Stefan Glienke 2009 Posted December 1, 2020 (edited) 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 December 1, 2020 by Stefan Glienke 2 Share this post Link to post
Mike Torrettinni 198 Posted December 1, 2020 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? 1 Share this post Link to post
David Heffernan 2347 Posted December 1, 2020 (edited) 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 December 1, 2020 by David Heffernan 1 1 Share this post Link to post
Lars Fosdal 1792 Posted December 1, 2020 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. 1 Share this post Link to post
Mike Torrettinni 198 Posted December 1, 2020 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
Lars Fosdal 1792 Posted December 1, 2020 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
Mike Torrettinni 198 Posted December 1, 2020 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
Clément 148 Posted December 1, 2020 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
David Heffernan 2347 Posted December 1, 2020 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
Lars Fosdal 1792 Posted December 1, 2020 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; 1 Share this post Link to post
Lars Fosdal 1792 Posted December 1, 2020 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; 3 Share this post Link to post
Lars Fosdal 1792 Posted December 1, 2020 Q: Is there a point to slapping Inline; on after Static; ? Share this post Link to post
David Heffernan 2347 Posted December 1, 2020 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
Lars Fosdal 1792 Posted December 1, 2020 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
David Heffernan 2347 Posted December 1, 2020 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. 1 Share this post Link to post
Anders Melander 1795 Posted December 1, 2020 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