Unhappy with MAX()/MIN() parameter types and return types

Hi folk,
maybe someone of you has a better idea.
In OpenVMS PASCAL one can write

myInteger := MAX(p1,p2,p3,p4,p5,p6); // where as p1 to p6 can be off different type and the return is Integer
myDouble := MAX(p1,p2,p3); // where as p1 to p3 can be off different type and the return is Double

in other words,this routines are library routines, and can be used in any direction, not caring on input parameter types, they can be mmixed, as long as they are numeric, and not caring about return type as long as the target parameter is numeric.

I don’t know how they made that at DEC. But I know that my boss hase made heavy use of it; Now I have a problem; But I ask if someone has an Idea how I can make this happen for our big porting project from OpenVMS PASCAL to Oxygene PASCAL without touching existing lines of code but adding wrapper routines MIN and MAX accepting, doing and retunrning like the OpenVMS PASCAL MIN MAX functions.

And to mention the moste lazy one’s: All MIN / MAX nice .NET Math.* routines accept exactly 2 parameters and they need to be of the same type, also the return type is given.

I have a solution for varying amount of parameters and types which works, but the routines need to return the proper types, and here I can not overload in Oxygene, because Overloading works on parameter types;

But I need MAX(params values:array of Object):Int32; So I am passing always an array of objects because the input is of different type and amount; And so Oxygene can not distinguishe which routine to call; as long as it does not also car about the return type to deliver.

Any great ideas is very welcome.
Josef

So to be clear, you want something that uses the target type instead of the input types?

The only things in Oxygene that actually work with result types are compiler expressions, like arrays, tuples. There might be something you could do with aspects, I’ll try to cook something up and let you know.

One thing you can do is this:

namespace ConsoleApplication53;

interface

uses
  System.Collections.Generic,
  System.Linq,
  System.Text;

type
  MaxValues = public class
  private
    fValues: array of Object;
  protected
  public
    constructor(aValues: array of Object);

    class operator Implicit(val: MaxValues): Double;
    class operator Implicit(val: MaxValues): Integer;
    class operator Implicit(val: MaxValues): Int64;
  end;
{$G+}

method Max(params vals: array of Object): MaxValues;
 
implementation

constructor MaxValues(aValues: array of Object);
begin

end;

class operator MaxValues.Implicit(val: MaxValues): Double;
begin
  result := Convert.ToDouble(val.fValues[0]);
  for i: Integer := 1 to length(val.fValues) -1 do
    result := Math.Max(result, Convert.ToDouble(val.fValues[i]));
end;

class operator MaxValues.Implicit(val: MaxValues): Integer;
begin
  result := Convert.ToInt32(val.fValues[0]);
  for i: Integer := 1 to length(val.fValues) -1 do
    result := Math.Max(result, Convert.ToInt32(val.fValues[i]));
end;

class operator MaxValues.Implicit(val: MaxValues): Int64;
begin
  result := Convert.ToInt64(val.fValues[0]);
  for i: Integer := 1 to length(val.fValues) -1 do
    result := Math.Max(result, Convert.ToInt64(val.fValues[i]));
end;
method Max(params vals: array of Object): MaxValues;
begin
  exit new MaxValues(vals);
end;

end.

Use it like:
  var d: Double := Max(1,2,3,4,5);
  var zd: Int64 := Max(1,2,3,4,5);

Method aspects can optimize the code to do the Max call inline (though you’ll need a newer compiler for that to work due to a bug in the aspect code for operators)

The alternative is to use a method aspect (note, this requires the upcoming beta/release):

namespace ClassLibrary1;

interface
uses
  RemObjects.Elements.Cirrus.*;

type
  OperatorResult = public class
  private
  protected
  public
    constructor(args: array of Object); empty;

    class operator Implicit(op: OperatorResult): Double; 
    class operator Implicit(op: OperatorResult): Integer; 
  end;

  [MethodAspect(typeOf(OperatorResult), true)]
  OperatorAspect = public class(IMethodCallDecorator)
  public
    method ProcessMethodCall(aContext: IContext; aMethod: IMethod; aValue: RemObjects.Elements.Cirrus.Values.ParameterizedValue): RemObjects.Elements.Cirrus.Values.Value;
  end;

{$G+}
method Max(params args: array of Object): OperatorResult;
  
implementation

method OperatorAspect.ProcessMethodCall(aContext: IContext; aMethod: IMethod; aValue: RemObjects.Elements.Cirrus.Values.ParameterizedValue): RemObjects.Elements.Cirrus.Values.Value;
begin
  var lProc := ProcValue( aValue.Parameters[0]);
  if lProc = nil then raise new Exception('Parameter isn''t an proc');
  var lParams := ArrayValue(lProc.Parameters[0]);
  if lParams = nil then raise new Exception('Parameter isn''t an array');
  if lParams.Count = 0 then raise new Exception('At least 1 parameter required');
  for i: Integer := 0 to lParams.Count -1 do begin 
    var lItem := lParams[i];
    if not assigned(lItem) then raise new Exception('No parameter!');
    if (lItem.Kind = ValueKind.UnValueOp) and (UnaryValue(lItem).Operator = UnaryOperator.Cast) then 
      lItem := UnaryValue(lItem).SubValue;
    lParams[i] := new UnaryValue(lItem, UnaryOperator.Cast, aMethod.Result); // cast to the operators result type.
  end;
  var lValue := lParams[0];
  for i: Integer := 1 to lParams.Count -1 do
    lValue := new ProcValue(new TypeValue(aContext.Services.GetType('System.Math')), 'Max', lValue, lParams[i]);
  exit lValue;
end;


method Max(params args: array of Object): OperatorResult;
begin
  raise new InvalidOperationException('Should not really be called!');
end;

class operator OperatorResult.Implicit(op: OperatorResult): Double;
begin
  raise new InvalidOperationException('Should not really be called!');
end;

class operator OperatorResult.Implicit(op: OperatorResult): Integer;
begin
  raise new InvalidOperationException('Should not really be called!');
end;

end.

How it works:

  • MethodAspect(typeOf(OperatorResult), true) << attaches the OperatorAspect to any static method call on OperatorResult, this will trigger for the implicit operators on the OperatorResult class.
  • ProcessMethodCall then gets called any time the implicit operators are used, or would have been used
  • The code in that method grabs the parameters passed to the implicit overload, which will be another ProcCall to Max() and grabs that one’s parameter, which is an ArrayValue (Because of the params).
  • The Oxygene compiler has added casts to Object() around which we remove and instead we add a cast to the target type (the result of the operator)
  • then we call Math.Max for each pair, ending up (in ilspy) as:
double de = Math.Max(Math.Max(Math.Max(1.0, 2.0), 3.0), 5.0);

Hi CK, thank you very much.
In absence of your marvelous solutions, and as we do not have classes to compile but mainly 30 year old spagetti code with MAX/MIN all arround, I went for the following aproach which at least fixed part of my problem.

I have implemented MAXI/MINI and MAXD/MIND according to the wanted return type!

I’mDouble := MAX(3, 4.6. abc. def.xyz, gaga,10.00, 4.4-4e);
shall be convert to
I’mDouble := MAXD( [ 3, 4.6. abc. def.xyz, gaga,10.00, 4.4-4e ] ); // lets simply pass them as objects in a array

So I defined an interface, among others …

FUNCTION MAXD(params values:array of Object):Double;

// MAX and overloads (overloads)
FUNCTION MAX(params values:array of Object):Double;
 var oplen:Integer;
 cache:Double;
 input:Double;
 begin        
   oplen:=values.GetLength(0);  
   if oplen=0 then cache:=Double.MinValue else cache:=Convert.ToDouble(values[0]);
   for i:Integer := values.GetLowerBound(0) to values.GetUpperBound(0) do begin
     input := Convert.ToDouble(values[i]); 
     if input > cache then cache := input;
   end;
   Result := cache;
end;
 // MAXi Wrappers
 FUNCTION MAXI(i1,i2:Int32):Int32;
 begin
     result := Convert.ToInt32(MAX([i1,i2]));    
 end;
 FUNCTION MAXI(params values:array of Object):Int32;
 begin
   result := Convert.ToInt32(MAX(values));    
 end;
 // MAXd Wrappers
  FUNCTION MAXD(d1,d2:Double):Double;
 begin
    result := MAX([d1,d2]);    
 end;
 FUNCTION MAXD(params values:array of Object):Double;
   begin
      result := MAX(values);    
 end;

given I have more then 2 actual parameters, then the type does’nt mater, I just pass it as an array of objects.

The only thing I was unable to master is that I can not find out at compile or runtime the return type wanted; hence I need MINI/MAXI and MIND/MAXD functions and have a look myself. (In fact I am writing a transformator using the nice www.txl.ca stuff to sorce-code-language-transform from OpenVMKS PASCAL to Oxygene PASCAL.)

When I understand Aspect’s, I hope that your solution will fix my problem in the future.
But until then I still have to serve 3GL brains; hence I can not make it to complicate for me and others. :wink:
Thank you.

1 Like

Cool. Note that the first solution I posted does not depend on aspects, and should work already.

So if you know a way how to find out inside a function body, the type to return at runtime, that would fix my problem and then I can go back to MAX/MIN and pass all different types by an Array of Objects.

Did you look at my first example? It does exactly this by using a class & explicit operator overload