Thursday 23 August 2007

Have an iif() function in Delphi

There are many advantages to learning other programming languages, but there are also some problems as well. The "Immediate If" (iif) function is one of those. In most, if not all, the other languages there is an immediate if function that makes programming just a little easier and allows you to say things in much less lines. Consider the following...

    if Str = 'Top' then
s := 'Woohoo!'
else
s := 'Climb some more';


While in Delphi we can effectively write this on one line as...

    if Str = 'Top' then s := 'Woohoo!' else s := 'Climb some more';


This looks untidy. If we were using another language with immediate if, that would look like this...

    s := iif(Str='Top', 'Woohoo!', 'Climb some more');


Now I realise to some Delphi programmers that are not multiple programming language experienced, all that seems a little esoteric but it is standard use in even Excel. Here's how it works...

iif(condition, TrueResult, FalseResult) : String;


The iif() function first looks at the boolean condition. If that condition is true, then it return the TrueResult, and if it is false, then it returns the FalseResult. Here's the declaration

function iif(Test: boolean; TrueR, FalseR: string): string;


The 1st parameter is the Test condition, this can be anything (e.g.: x > y).
The 2nd parameter is the string to return if the test condition is True.
The 3rd parameter is the string to return if the test condition is False.

Here's the full function...

function iif(Test: boolean; TrueR, FalseR: string): string;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;


but in other languages, the 'type' we return can vary. For example we may want to return 99 if true or 0 of false. How do we do that in Delphi? By using the 'overload' directive.

function iif(Test: boolean; TrueR, FalseR: string): string; overload;
function iif(Test: boolean; TrueR, FalseR: integer): integer; overload;
function iif(Test: boolean; TrueR, FalseR: extended): extended; overload;

Implementation

function iif(Test: boolean; TrueR, FalseR: string): string;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;

function iif(Test: boolean; TrueR, FalseR: integer): integer;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;

function iif(Test: boolean; TrueR, FalseR: extended): extended;
begin
if Test then
Result := TrueR
else
Result := FalseR;
end;


We can even extend these to bytes or even TObject if we want to. The only thing we can't do is have the same parameter types and return a different type, e.g...

function iif(Test: boolean; TrueR, FalseR: string): string; overload;
function iif(Test: boolean; TrueR, FalseR: string): integer; overload;


This is a no-no. Why? Delphi will decide which function to run based on the parameter types. If all the parameter types are the same, Delphi will not compile as it will see this as an error.

As already stated, Delphi decides which function it will used based on the types used in the parameters. It will always use the least type for the job. For example, if you have iif()'s set up for both integer and byte, and then use iif(x=y,5,10), then Delphi will use the byte function and return a byte. In most cases this will still work ok even if you are expecting an integer returned.

14 comments:

  1. Thanks to a quick note from Dennis Gurock, Delphi does have a similar iif function defined as IfThen(), available in the Math unit.

    Math.IfThen() works in the same way as the iif() function defined here, but returns Integer, Int64, or Double, depending on the parameters given.

    Thanks Dennis.

    ReplyDelete
  2. FYI: A Math implementation of IfThen() has been in the Delphi RTL since 7.0 I think, or perhaps earlier, but doesn't provide a complete set of overloaded versions.

    Also something to be VERY cautious of when using these in Delphi is that all parameters have to be evaluated BEFORE your IIF() or IfThen() function can be called. So this sort of thing will cause problems:

    count := IIF( Assigned(list), list.Count, 0);


    This (I think) is different from the most oft quoted equivalent - the ? or tertiary operator in C/C++, where if memory serves, only the argument determined by the condition is evaluated.

    IIF/IfThen is nice. A proper tertiary operator would be even nicer.

    :)

    ReplyDelete
  3. There is also a iif() function in IdGlobal.pas, which is part of Indy, which is in Delphi since D6 (iirc).

    ReplyDelete
  4. There is also a IfThen function declared in StrUtils that returns a string. If you use StrUtils and Math units then you could use both functions like:

    str := IfThen([BoolExpr], [StrExpr1], [StrExpr2]);

    or:

    i := IfThen([BoolExpr], [IntExpr], [IntExpr]);

    Nevertheless, as joylon said, I think it lacks of a complete set of overloaded versions.

    But what could you say about an EVAL(code: string) function? this little function is an interpreter itself of whatever code in form of string that you pass to it as its parameter. This is a function of Clipper (exactly, CA-Clipper) back in the nineties.

    Of course, Clipper use to embed itself in every executable. :P

    ReplyDelete
  5. Eval is evil, ask any experienced javascript programmer. I'd rather be forced to do the right thing by not having eval than having it sit there and tempt me to cut corners.

    ReplyDelete
  6. Jolyon Smith point out a real problem with a non Codegear implementation of iff(), that is that both result parameters will evaluate. Compare that to the IF statement... and I will choice the IF statement if I suspect a possible expensive performance problem.

    It would be be a cheery day in the bullpen if Codegear would add an iff() function with delayed parameter evaluation.

    ReplyDelete
  7. Some very good posts there. I remember the Eval function in Clipper and used it in some tricky areas. It was nice for what it did.

    Please, please take note of Jolyn Smith's comments. The IIF functions described here and in the mentioned units are not a tru, or even close relationship to a fully implemented IIF in the language itself.

    I enthusiastically, jumping up and down yelling even, urge CodeGear to implement iif in the base Pascal language.

    But if we are suggesting altering the base Pascal, how will that effect the "Pascal Sentence"? (see earlier blog) The Pascal Sentence is the underlying philosophy of Pascal itself. Perhaps this is why IIF was never implemented as it can be arued that it disrupts the smooth flow of language.

    However, I suggest that the Pascal Sentence will not be disrupted at all. Pascal has grown well beyond the 20 odd words it used in Turbo Pascal v1. In fact the addition of the immediate if into the base Pascal language itself is well and truly overdue.

    ReplyDelete
  8. As already pointed out, both expressions are evaluated. However, it isn't just a performance problem, it's also a technical problem, you simply can't write some statements, e.g.

    x := iff( mypointer=nil, 0, mypointer^.z);

    will result in Access Violation if mypointer is nil. Pity.

    ReplyDelete
    Replies
    1. x := iif(not Assigned(mypointer), 0, ...

      Delete
  9. Instead of use an overloaded version of the same function, I used a "variant" version:

    function iif(cond:boolean;ifT:Variant;ifF:Variant):Variant;

    This is good for Database applications where the value coming from tables are variants.

    ReplyDelete
  10. If the IIF() function was created with "inline" directive (available in D2005+ i think), would that get over the limitations mentioned above?
    (Since the function will be expanded inline, as if a real IF statement was written).

    It's worth a try to see if it works :)

    ReplyDelete
  11. I just implemented it using variants:

    function iif(Test: boolean; TrueR, FalseR: variant): variant;
    begin
     if Test then
      Result := TrueR
     else
      Result := FalseR;
    end;

    ReplyDelete
  12. You can minimize overloads with generics:

    type
    iif = class
    public
    class function go(fpCondition: boolean; fpOnTrue, fpOnFalse: W): W;
    end;


    class function iif.go(fpCondition: boolean; fpOnTrue, fpOnFalse: W): W;
    begin
    if fpCondition then
    result := fpOnTrue
    else
    result := fpOnFalse;
    end;


    Usage:
    iif.go(1 = 1, 'YES', 'NO')
    iif.go( 1 = 2, 1, 0)

    :)

    ReplyDelete
  13. If found Eval and as far as i remenber codeblocks:
    DbEval({|x,y,z| myfunction_form_etc(P1,P2,P3) }); It was the most powerfull thing i've ever worked.I had made a full application
    all parameterized from menus, tbrowse etc... all carried from
    saved parameter files and running on fly.
    Do you know abaou some dbeval classes in from delphi7 to XE7
    I never heard of it!
    It's a pittty we have nothing like it...
    Don't you think so?

    ReplyDelete

Note: only a member of this blog may post a comment.