Recent

Author Topic: PoC: Lazy Evaluation  (Read 337 times)

Warfley

  • Hero Member
  • *****
  • Posts: 1872
PoC: Lazy Evaluation
« on: September 25, 2024, 09:57:33 pm »
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:
Code: Pascal  [Select][+][-]
  1. type
  2.   generic TLazy<T> = record
  3.   public type
  4.     TGeneratorFunc = reference to function: T;
  5.   private
  6.     HasValue: Boolean;
  7.     Value: T;
  8.     GenerateValue: TGeneratorFunc;
  9.   public
  10.     function GetValue: T;
  11.  
  12.     class operator :=(const gen: TGeneratorFunc): specialize TLazy<T>;
  13.     class operator :=(const val: T): specialize TLazy<T>;
  14.     class operator :=(const lazy: specialize TLazy<T>): T;
  15.   end;
  16.  
  17. function TLazy.GetValue: T;
  18. begin
  19.   if not HasValue then
  20.   begin
  21.     Value := GenerateValue();
  22.     HasValue:=True;
  23.   end;
  24.   Result := Value;
  25. end;
  26.  
  27. class operator TLazy.:=(const gen: TGeneratorFunc): specialize TLazy<T>;
  28. begin
  29.   Result.GenerateValue := Gen;
  30.   Result.HasValue := False;
  31. end;
  32.  
  33. class operator TLazy.:=(const val: T): specialize TLazy<T>;
  34. begin
  35.   Result.Value := val;
  36.   Result.HasValue := True;
  37. end;
  38.  
  39. class operator TLazy.:=(const lazy: specialize TLazy<T>): T;
  40. begin
  41.   Result := lazy.GetValue;
  42. 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:

Code: Pascal  [Select][+][-]
  1. generic function IfThenLazy<T>(Cond: Boolean; TrueVal, FalseVal: specialize TLazy<T>): T;
  2. begin
  3.   if Cond then
  4.     Result := TrueVal
  5.   else
  6.     Result := FalseVal;
  7. 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:
Code: Pascal  [Select][+][-]
  1. function NextInt: Integer;
  2. const i: Integer = 0;
  3. begin
  4.   Result := i;
  5.   Inc(i);
  6.   WriteLn('NextInt');
  7. end;
  8.  
  9. begin
  10.   WriteLn('Lazy:');
  11.   WriteLn(specialize IfThenLazy<Integer>(True,
  12.                 function:Integer begin Result:=NextInt end,
  13.                 function:Integer begin Result:=NextInt end
  14.          ));
  15.   WriteLn('Classic:');
  16.   WriteLn(IfThen(True, NextInt, NextInt));
  17. end.
  18.  
Result:
Quote
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:
Code: Pascal  [Select][+][-]
  1. uses params := result
  2. // e.g.
  3. AddFunc := uses x, y := x + y
If that would be implemented with a simple macro, you could make this as clean as:
Code: Pascal  [Select][+][-]
  1. {$Macro Lazy:=uses:=}
  2. ...
  3.   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

 

TinyPortal © 2005-2018