As there was recently another Thread about
Ternary if-then-else Expressions, and it was mentioned that Pascal does not have a Equivalent to C's operator because of the double evaluation problem, I figured, why not build a Lazy if-then-else.
In theory it's very simple, just instead of passing a value, pass a generator expression, which will be called when needed. This can be neatly enclosed in a wrapper type like this:
type
generic TLazy<T> = record
public type
TGeneratorFunc = reference to function: T;
private
HasValue: Boolean;
Value: T;
GenerateValue: TGeneratorFunc;
public
function GetValue: T;
class operator :=(const gen: TGeneratorFunc): specialize TLazy<T>;
class operator :=(const val: T): specialize TLazy<T>;
class operator :=(const lazy: specialize TLazy<T>): T;
end;
function TLazy.GetValue: T;
begin
if not HasValue then
begin
Value := GenerateValue();
HasValue:=True;
end;
Result := Value;
end;
class operator TLazy.:=(const gen: TGeneratorFunc): specialize TLazy<T>;
begin
Result.GenerateValue := Gen;
Result.HasValue := False;
end;
class operator TLazy.:=(const val: T): specialize TLazy<T>;
begin
Result.Value := val;
Result.HasValue := True;
end;
class operator TLazy.:=(const lazy: specialize TLazy<T>): T;
begin
Result := lazy.GetValue;
end;
This type allows to store either a value, or a generator for a value, and when trying to get the value it will first check if the value exists, if not it is freshly generated. Through overloading the Assignment operator, it can be transparently used as the type it is encapsulating. The resulting lazy if-then-else looks like this:
generic function IfThenLazy<T>(Cond: Boolean; TrueVal, FalseVal: specialize TLazy<T>): T;
begin
if Cond then
Result := TrueVal
else
Result := FalseVal;
end;
As you can see, with the transparent usage through the assignment operators, except for the TLazy in the function header, it looks like the normal if-then-else definition we get e.g. in the math unit.
And see the usage:
function NextInt: Integer;
const i: Integer = 0;
begin
Result := i;
Inc(i);
WriteLn('NextInt');
end;
begin
WriteLn('Lazy:');
WriteLn(specialize IfThenLazy<Integer>(True,
function:Integer begin Result:=NextInt end,
function:Integer begin Result:=NextInt end
));
WriteLn('Classic:');
WriteLn(IfThen(True, NextInt, NextInt));
end.
Result:
Lazy:
NextInt
0
Classic:
NextInt
NextInt
2
As you can see, unlike the classical if-then-else, the NextInt is only called once, for the chosen argument, and not twice for each argument. So it works like the ternary if-then-else in C.
Only downside is the horrendous anonymous function syntax we inherited from Delphi. There were in the past talks of having single expression lambdas like this:
uses params := result
// e.g.
AddFunc := uses x, y := x + y
If that would be implemented with a simple macro, you could make this as clean as:
{$Macro Lazy:=uses:=}
...
IfThenElseLazy(True, lazy NextInt, lazy NextInt);
Which would solve this eternal discussion, without having to add a new kind of operator/intrinsic. And would allow to build even more applications utilizing lazy evaluation of expressions