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.
精彩评论