开发者

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

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
  ...&开发者_运维问答lt;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?


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.


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.


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.


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.


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]


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


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.


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.


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


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/


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;


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.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜