Is there, or is there ever going to be, a conditional operator in Delphi?
Asked Answered
K

12

33

I kept my hands off Delphi for too long, I guess; busied myself with Java and PHP a lot over the last couple of years. Now, when I got back to doing a little Delphi job, I realised I really miss the conditional operator which is supported by both Java and PHP.

On how many places would you find lines like these in your Delphi programs?

var s : string;
begin
  ...<here the string result is manipulated>...

  if combo.Text='' then
      s := 'null'
    else
      s := QuotedStr(combo.Text);

  result := result + s;
end;

where a simple

result := result + (combo.text='')?'null':quotedStr(combo.text);

would suffice. What I like about this is that it not only shortens the code, this way I also avoid declaring some helper s:string variable.

Why are conditional operators not part of Delphi and - are they ever going to be supported? I noticed there were quite a few language extensions made for the 2009 version of Delphi (generics), so why not add this feature?

Kentigera answered 21/1, 2010 at 10:59 Comment(8)
The correct name and tagging is "conditional operator".Leibniz
|I think it can be called either Daniel: msdn.microsoft.com/en-us/library/e4213hs1%28VS.71%29.aspx or en.wikipedia.org/wiki/Ternary_operationIrredentist
agree with mrPeregrination, but I re-tagged the question so all can be happy :)Rf
The conditional operator is A ternary operator, in the same way that '/' is a binary operator - you don't call '/' "the binary operator, do you?Courier
Also a nice discussion. But because there is only one common used ternary operator, the term is often used for the conditional operator. But you are right, it is still wrong.Treacle
The main reason why I pointed it out it that with only one tag "conditional-operator", the content will be better organized.Leibniz
I think the fundamental difference with something like generics is that generics make stuff doable that is not doable otherwise, while the ternary operator is a not very interesting shortcut.Lister
@Marco: Self evidently everything we do with generics can be done without them too, cos we got by without them for so long. They too are just a shortcut (and in some ways the implementation in Win32 Delphi is limited compared to the long-hand alternatives).Tannen
S
45

Such an operator isn't part of the current Delphi version because it wasn't part of the previous version, and demand wasn't great enough to justify the cost of adding it. (You'll find that explanation applies to lots of features you wish you had in lots of products.)

Delphi provides a set of IfThen functions in the Math and StrUtils units, but they have the unfortunate property of evaluating both their value parameters, so code like this will fail:

Foo := IfThen(Obj = nil, '<none>', Obj.Name);

To really do it right, there needs to be help from the compiler. Within the Delphi community, I sense a general dislike of the C-style syntax using a question mark and a colon. I've seen proposals that would use syntax like this:

Foo := if Obj = nil then
         '<none>'
       else
         Obj.Name;

Part of what makes conditional operators so attractive is that they let you write concise code, but Delphi's style of writing everything out makes the above unappealing, even if put all on one line.

It doesn't really need to be in the form of an operator. Delphi Prism provides a compiler-magic function Iif that only evaluates one of its two value parameters:

Foo := Iif(Obj = nil, '<none>', Obj.Name);

You asked why a feature like this wouldn't have been added along with all the other language features added in Delphi 2009. I think that's your reason. There were plenty of other language changes going on that already required delicate handling; the developers didn't need to be burdened with even more. Features aren't free.

You asked whether Delphi will ever have such a feature. I'm not privy to Embarcadero's planning meetings, and I had to send my crystal ball away for repairs, so I can't say for certain, but I predict that if it ever would have such a feature, it would come in the form of Delphi Prism's Iif function. That idea shows up near the end of the discussion in Quality Central, and an objection is made that, as a new reserved word, it would break backward compatibility with other people's code that already defines a function with the same name. That's not a valid object, though, because it wouldn't need to be a reserved word. It could be an identifier, and just like Writeln and Exit, it can be eligible to be redefined in other units even though the one from the System unit is treated specially.

Superclass answered 21/1, 2010 at 15:34 Comment(8)
Thanks for contribution, an interesting read. Hope your crystal ball comes back safe and sound and then you could tell us more about the future of ()?:Rf
One nice syntactic effect of the C ternary operator is the elegant concatenation of conditionals, you can write v=c1?a1:c2?a2:c3?a3:b current delphi IfThen syntax prevents it.Leflore
Whether that syntax effect is classified as "nice" or "elegant" is a matter of some debate, @Leflore I find your example to be an inscrutable blob of code. I cannot understand it on the first reading — I can't even parse it in one pass. Spaces and parentheses might help. I try to avoid having nested conditional operators.Superclass
@PA I'm with Rob on this one. If it makes the code more difficult to understand I wouldn't call it elegant. I would characterize your example as an overuse(abuse) of the comparison operator. It would be a useful technique for the Obfuscated C Code Contest though.Cheerless
Delphi has gained enough hairy features from the java and C++ world (generics, etc) to last me seven lifetimes. You can already make an unreadable mess without a ternary ('conditional') operator, and I think it would only depart further from the memories-of-Pascal readability that I still pine for.Grimaldi
Modern Delphi implementations of IfThen are inline and so only the chosen option is evaluatedMcknight
That news is huge, @David. The documentation doesn't mention it. It's the compiler's choice whether to expand an inline function, which means some invocations of IfThen might have both arguments evaluated while some will only have one. I'm still doubtful that what you've said is true, or if it is, that the behavior is intentional and not a bug in inline expansion.Superclass
@RobKennedy I'm talking rubbish. The compiler's inliner ensures that any argument that could have a side effect is always evaluated. The compiler writes code to evaluate it away and then stores it on the stack frame. Clearly it does that to avoid semantic changes caused by inlining choice. If the expression cannot have side effects, that it is evaluated on demand.Mcknight
P
6

Ok. WTF code of the day :)

How to get something that mostly acts like a ternary/conditional function.

program Project107;
{$APPTYPE CONSOLE}

uses SysUtils;

type
  TLazyIfThen<T:record>=record
    class function IfThen(aCondition:Boolean;aIfTrue, aIfFalse:TFunc<T>):T; static;
  end;

  class function TLazyIfThen<T>.IfThen(aCondition:Boolean;aIfTrue, aIfFalse:TFunc<T>):T;
  begin
    if aCondition then
      Result := aIfTrue
    else
      Result := aIfFalse
  end;

begin
  WriteLn(
    TLazyIfThen<Integer>.IfThen(
      True,
      function:Integer begin result := 0 end,
      function:Integer begin result := 1 end
    )
  );
  ReadLn;
end.

Yeah, it's more or less useless, but it shows that it can be done.

Proprioceptor answered 9/3, 2011 at 13:51 Comment(4)
I think you've misunderstood the point of the ?: operator -- to make the code more compact. ;)Tubbs
hehe.. delphi has never been a real winner in the code-golf competitions here.Proprioceptor
I started to write an answer and just saw that it was almost identical to this one. I think that class function IfThen<T> is a bit better, since the compiler will opt out all possible types not using TLazyIfThen. And you can let aIfTrue be of type T, just to save some typing. And agreed, it will not make code look pretty.Crosslet
@LURD: you can use T instead of TFunc<T>, but then of course you wouldn't get lazy evaluation via anonymous methods, and it would be more like a generic version of ifthen(). About writing it as ifthen<T>: do yo mean that it results in a smaller executable?Proprioceptor
S
5

There's a QC report on this (8451) which has a reasonable discussion.

Raised June 2004, and there doesn't appear to be any response from Borland/CodeGear/Embarcadero.

Squid answered 21/1, 2010 at 12:18 Comment(1)
It has 25 votes and a rating of 3.81; probably not enough to make it on the feature list of a new version.Schweiz
L
5

There are a number of available simple type handles on the overloaded IFTHEN function.

StrUtils.IfThen (String)

Math.IfThen (Integer)

Math.IfThen (Int64)

Math.IfThen (Double) (works for TDateTime as well)

This model falls down as shown in the example that Andreas commented on, but for simple types this is more than reasonable. If follows the Delphi/Pascal convention of methods rather than succumbing to the C way of using the least amount of characters as possible.

Personally I would rather not see a conditional operator (i.e. ?:) introduced in Delphi as I prefer the readability of Delphi/Pascal over C and it derivative languages. I would prefer to see more innovative Delphi type solutions to something like this than to implement more C-isms.

Librarianship answered 21/1, 2010 at 15:36 Comment(1)
mini-rant: I have seen very unreadable code with even very small usage of the ? ternary operator in C-like languages. The main cause is that it is yet another symbol being used, and most people using it forget to add parentheses or indentation for readability. The speed improvements that it can bring are hardly reached: most usage is just a replacement of a single if/then/else, whereas real improvement only can be gained in complex expressions that few people can maintain.Schweiz
T
4

There is no conditional operator in Delphi, and I seriously doubt if there will ever be one but you may never know. You can always issue a request at Embarcadero.

An alternative is to define the Iff function:

function Iff(const ACondition: Boolean; const ATrueValue, AFalseValue: XXX): XXX;
begin
  if ACondition then
    Result := ATrueValue
  else
    Result := AFalseValue;
end;

Where XXX is the desirec type.

Use as:

Result := Result + Iff(combo.text='', 'null', quotedStr(combo.text));

There are several reasons why not to implement the conditional operator. One of these is readability. Pascal (and also Delphi) is more centered on Readability than the C Syntax languages which are more centered on character power (as much information per character as possible). The conditional operator is powerful but (according to some) not readable. But if you look at the (dreaded) with statement in Delphi... (no need to say more). Another reason is that the conditional operator is not required. Which is true. But there is more not required that is still implemented.

In the end it's just a matter of taste.

But if you want just one argument to be evaluated, you can always use the folowing, which violates both the readability as the character power concept:

[overdesignmode]

// Please don't take this that serious.
type
  TFunc = function(): XXX;
function Iff(const ACondition: Boolean; const ATrueFunc, AFalseFunc: TFunc): XXX;
begin
  if ACondition then
    ATrueFunc
  else
    AFalseFunc;
end;

[/overdesignmode]

Treacle answered 21/1, 2010 at 11:4 Comment(14)
Unfortunately, this doesn't work like a conditional operator should, as all the functions parameters will be evaluated. A conditional operator should evaluate the test, and then only one of the two remaining expressions.Courier
I've seen this idea utilised before (in PL/SQL)... How would I write an Iff function that would operate with any data type (XXX)?Rf
Because Iff is a function both ATrueValue and AFalseValue are evaluated. So a "(o != null) ? o.GetValue() : null" can't be translated to "Iff(o <> nil, o.GetValue, nil)" because the "o.GetValue" would crash.Scrutineer
I'd guess many have already requested this from Embarcadero... or whoever happens to own it this year.Rf
I agree, it is not a perfect substitution. But rewriting the compiler is not that easy done. So we have to work with what we have got. You can, implement some kind of preprocessor. Or stick to the old ways.Treacle
Since Delphi recently got the exit with return value (which is also not strictly necessary, but has higher "character power") maybe there is some hope for the ternary operator too.Laverty
Is naming such a function as iff a common practice? I use to name them ifelse functions.Leflore
I have seen the use of iff multiple times. But names are often misused and reused to the point of max confusion.Treacle
Yes, it isn't required; but then again neither is the "case" statement :)Squid
The name Iff is wrong. That's the word for "if and only if," which is nonsense in this context. The name you want here is Iif, for "immediate if" or "inline if."Superclass
mghie: exit return value could as well be copied from Free Pascal which has had it since 1999 or so.Lister
"Unfortunately, this doesn't work like a conditional operator should, as all the functions parameters will be evaluated." You can "inline" this function. In this case there will not be a real call.Juvenescence
No, Torbins, you can't "inline" it. If you inline it manually, you're right back to where you started, with a full-fledged if statement. If you rely on the compiler to inline it, it's still going to honor all the function-call-evaluation semantics, which dictate the the actual arguments of a function get fully evaluated before evaluating any of the function's contents.Superclass
The "dreaded with" can be used safely and sensibly to make code MORE readable, by eliminating redundant repetition, e.g. with cmbNames do ItemIndex := Items.IndexOf[sSomeName] vs cmbNames.ItemIndex := cmbNames.Items.IndexOf[sSomeName]. As always with dogma, there IS a need to say more if we are to have a sensible discussion, rather than just parrot the dogma.Tannen
E
4

I would prefer them to to implement lazy evaluation and it will be more powerful and can be used in different scenario. See detail as below link

http://www.digitalmars.com/d/2.0/lazy-evaluation.html

Cheers

Ehf answered 31/1, 2010 at 17:20 Comment(0)
P
2

Another option is to use generics:

Cond<T> = class
    public class function IIF(Cond: boolean; IfVal: T; ElseVal: T): T;
  end;

implementation

class function Cond<T>.IIF(Cond: boolean; IfVal, ElseVal: T): T;
begin
  if Cond then
    Result := IfVal
  else
    Result := ElseVal;
end;

This is quite readable:

var MyInt: Integer;
begin
  MyInt:= Cond<Integer>.IIF(someCondition, 0, 42);

note: as pointed out by Alan Bryant (at 6/21/2004 7:26:21 AM) in QR 8451, this will always evaluate all 3 arguments - so be aware that it's not a true ternary operator.

Precondition answered 19/11, 2013 at 13:26 Comment(0)
B
1

Actually for strings you can use the: StrUtils.IfThen function:

function IfThen(AValue: Boolean;
        const ATrue: string;
        AFalse: string = ): string; overload;

Look in the delphi help wiki: http://docwiki.embarcadero.com/VCL/en/StrUtils.IfThen

It does exactly what you need.

Beedon answered 21/1, 2010 at 15:19 Comment(2)
I'll use this, just to try something 'new' :)Rf
No, it doesn't. If the ATrue and AFalse values passed to the function are functions, then both true and false functions will be executed and evaluated before IfThen() is called. The conditional operator only evaluates the expression corresponding to either the true or false branches, depending on the conditional expression.Tannen
D
0

Better yet is an overloaded IIF (inline if) that supports multiple datatypes and results.

Danita answered 23/1, 2010 at 9:50 Comment(1)
There are already a few IfThen() routines in the StrUtils and Math units. They are not the same as a conditional operator because the IfThen() routines are functions with parameters. All parameters are evaluated when the function is called. Often only 1 parameter is valid so the call to the function will crash. Imagine calling IfThen(false, ClassA.X, ClassB.X) with ClassB being nil. A conditional operator should only evaluate the part that is determined by the condition and ignore the other part.Misogamy
C
0

Jedi Code Library ( JCL ) has implemented the ternary operaror with a set of functions named Iff(). See here for documentation:

http://wiki.delphi-jedi.org/wiki/JCL_Help:Iff@Boolean@Boolean@Boolean

To download JCL you can visit this site:

http://sourceforge.net/projects/jcl/

Chameleon answered 7/3, 2011 at 13:54 Comment(1)
Almost -1. As these are ordinary functions, there is no "lazy" evaluation, which is an imperative ingredient of the ?: operator.Tubbs
O
0

WTF code of the day Nr. 2:

program TernaryOpTest;

uses
  System.SysUtils, Vcl.Dialogs;

type
  TGetValue = reference to function(): Double;

function TernaryOp(condition: Boolean; trueFunc, falseFunc: TGetValue): Double;
begin
  if condition then begin
    if Assigned(trueFunc) then Result := trueFunc() else raise EArgumentNilException.Create('trueFunc not defined.');
  end
  else begin
    if Assigned(falseFunc) then Result := falseFunc() else raise EArgumentNilException.Create('falseFunc not defined.');
  end;
end;

procedure TernaryTest(x: Double);
var
  v: Double;
begin
  v := TernaryOp(x <> 0, function(): Double begin Result := 1/x; ShowMessage('True case'); end, function(): Double begin Result := 0; ShowMessage('False case'); end);
  ShowMessage(FloatToStr(v));
end;

begin
  ShowMessage('Testing true case');
  TernaryTest(10);
  ShowMessage('Testing false case');
  TernaryTest(0);
  ShowMessage('Testing exception');
  TernaryOp(False, nil, nil);
end.

One possible modification for Variant data type is:

type
  TGetValue = reference to function(): Variant;

function TernaryOp(condition: Boolean; trueFunc, falseFunc: TGetValue): Variant;
begin
  Result := Unassigned;
  if condition then begin
    if Assigned(trueFunc) then Result := trueFunc();
  end
  else begin
    if Assigned(falseFunc) then Result := falseFunc();
  end;
end;
Orlena answered 3/6, 2019 at 12:18 Comment(0)
S
0

In case it helps, most benefit over Math.IfThen functions is that it uses "inline" (assuming compiler honors it).

The version with TFunc or TFunc<T,T> would be a bit ugly when using since Delphi doesn't have Lambda expressions (though I found some experiment with Variant expressions) for its anonymous methods

unit Zoomicon.Generics.Functors;

interface
  uses SysUtils; //for TPredicate

type
  TF = class
    class function Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: T): T; overload; inline;
    class function Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: TFunc<T>): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: T): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T>): T; overload; inline;
    class function Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T,T>): T; overload; inline;
  end;

implementation

class function TF.Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: T): T;
begin
  if Condition then
    result := ValueIfTrue
  else
    result := ValueIfFalse;
end;

class function TF.Iff<T>(const Condition: Boolean; const ValueIfTrue, ValueIfFalse: TFunc<T>): T;
begin
  if Condition and Assigned(ValueIfTrue) then
    result := ValueIfTrue
  else
    result := ValueIfFalse; //Note: will fail if ValueIfFalse is not assigned
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: T): T;
begin
  if Assigned(Condition) then
    result := Iff(Condition(Value), ValueIfTrue, ValueIfFalse)
  else
    result := ValueIfFalse;
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T>): T;
begin
  //result := Iff(Value, Condition, ValueIfTrue(), ValueIfFalse()); //use of () seems to be needed else compiler seems to match the same function (infinite recursion) //DOESN'T COMPILE (probably Delphi bug)
  if Assigned(Condition) then
    result := Iff(Condition(Value), ValueIfTrue, ValueIfFalse) //TODO: not sure if evaluation is deferred here (aka which "Iff" gets called [CTRL+Click in Delphi doesn't seem that clever], @ValueIfTrue/@ValueIfFalse to force that don't seem to compile)
  else
    result := ValueIfFalse; //Note: will fail if ValueIfFalse is not assigned
end;

class function TF.Iff<T>(const Value: T; const Condition: TPredicate<T>; const ValueIfTrue, ValueIfFalse: TFunc<T,T>): T;
begin
  //result := Iff(Value, Condition, ValueIfTrue(Value), ValueIfFalse(Value)); //DOESN'T COMPILE (probably Delphi bug)
  if Assigned(Condition) and Assigned(ValueIfTrue) {and Assigned(ValueIfFalse)} then //no need to check Assigned(ValueIfFalse) here, since in any case it will fail
    result := Iff(Condition(Value), ValueIfTrue(Value), ValueIfFalse(Value)) //Note: will fail if ValueIfFalse is not assigned
  else
    result := ValueIfFalse(Value); //Note: will fail if ValueIfFalse is not assigned
end;

end.
Sletten answered 21/10, 2021 at 10:29 Comment(1)
can find under github.com/Zoomicon/READCOM_App (an app under development) at Zoomicon.Generics for nowSletten

© 2022 - 2024 — McMap. All rights reserved.