Dynamic Arguments To Procedures
Reading on stackoverflow.com I saw the desire to have parameters like substitute() - that is, an unlimited number of parameters, but only when they are needed. Since Progress already has discovered a need for a tool like this, I'm surprised they haven't made such a thing available to the ABL/4GL. Even I have found myself needing a set of parameters to go to a procedure and frankly, did not do it.
NOT ANYMORE! TODAY I WORKED ON n NUMBER OF PARAMETERS TO GO TO A PROCEDURE!
Personally I would not have done it this if the 4GL was programmer adaptable, but you work with what you got and can.
Implementation
So, basically one works like this:
display Powers ("A 10 as int,B 2 as int").
I want to have two parameters and they are both integer type. As you can see, it all goes into a specially formatted string to the function. This adaptation can go after one puts in normal parameters - or all can go into the parameter.
function Powers returns decimal(input Arg as character):
?
? define variable DynArg as DynArgs no-undo.
? define variable A as integer no-undo.
? define variable B as integer no-undo.
?
? DynArg = new DynArgs(Arg).
?
? assign
? A = integer(DynArg:ByName("A"))
? B = integer(DynArg:ByName("B"))
? .
?
? return exp(A,B).
?
end.
So above one can see all the parameters go into a string (32K limit) or a longchar (really constrained by memory size!)
We have an object named DynArgs() which will feed information from the inputs.
There are two ways of feeding the functions arguments:
define variable D as DynArgs no-undo.
D = new DynArgs("").
display Powers(D:SetParameter("A", "10", "int") + "," + D:SetParameter("B", "2", "int")).
or the more understandable
display Powers ("A 10 as int,B 2 as int").
Both of them can be used. Any type can be made, but the coding is a little weak for character strings. (That can be fixed in a later edition of this!)
Seeing from the above function definition one can use the following to nab the args from the common string:
? define variable A as integer no-undo.
? define variable B as integer no-undo.
assign
? A = integer(DynArg:ByName("A"))
? B = integer(DynArg:ByName("B"))
? .
.
This is the heart of the coding for implementing - it simply pulls out the data and gives A and B their values.
But how does the object get the arguments?
function Powers returns decimal(input Arg as character)
? define variable DynArg as DynArgs no-undo.
? DynArg = new DynArgs(Arg).
From the above we pass in the definable character string as Arg. This then acts as the input to the object, which will break it into pieces to get the arguments from.
It only returns characters for the values so one will need a function to make them the actual value. (A weakness in OOP programming, only one type can be returned.)
After A and B are returned, we send them into exp() for use.
The basics for the class are shown below:
using Progress.Lang.*.
block-level on error undo, throw.
class DynArgs:
? define temp-table Args
??? field TheName as character
??? field TheValue as character
??? field TheType as character.
?? ?
? define property NumberOfParameter as integer get. set.
? define property TheArgs as character get. set.
?
? //? A 5 as int, B 6 as int
? constructor public DynArgs (input A as character):
?? ?
??? define variable Iter as integer no-undo.
??? define variable TheParameter as character no-undo.
?????? ?
??? NumberOfParameter = num-entries(A, ",").
??? TheArgs = A.
?? ?
??? do Iter = 1 to NumberOfParameter:
???? ?
????? TheParameter = trim(entry(Iter, A)). // Help with spacing issues
???? ?
????? message TheParameter.
???? ?
????? create Args.
???? ?
????? assign
????? Args.TheName = entry(1, TheParameter, " ").
????? Args.TheValue = entry(2, TheParameter, " ").
????? Args.TheType = entry(4, TheParameter, " ").
????? .
???? ?
??? end. // do
??? //message "Done".
? end constructor.
? method public void ShowParameter():
?? ?
??? for each Args:
????? display Args.
??? end.
?? ?
? end.
?
? method public character ByName (ValueName as character):
?
??? find Args no-lock
??? where Args.TheName = ValueName
??? no-error.
?? ?
??? if available Args then return Args.TheValue.
?? ?
??? return ?.
?? ?
? end.? // ByName
?
? method public character TheType (ValueName as character):
?? ?
??? find Args no-lock
??? where Args.TheName = ValueName
??? no-error.
?? ?
??? if available Args then return Args.TheType.
?? ?
??? return ?.
?? ?
? end. // TheType
?
? method public character SetParameter (VarName as character, VarValue as character, VarType as character):
?? ?
??? return VarName + " " + VarValue + " as " + VarType.
?? ?
? end.
?
end class. // DynArgs
And the implementation as a whole:
function Powers returns decimal(input Arg as character):
?
? define variable DynArg as DynArgs no-undo.
? define variable A as integer no-undo.
? define variable B as integer no-undo.
?
? DynArg = new DynArgs(Arg).
?
? assign
? A = integer(DynArg:ByName("A"))
? B = integer(DynArg:ByName("B"))
? .
?
? return exp(A,B).
?
end.
define variable D as DynArgs no-undo.
D = new DynArgs("").
display Powers ("A 10 as int,B 2 as int").
display Powers(D:SetParameter("A", "10", "int") + "," + D:SetParameter("B", "2", "int")).
Pretty good for a starting point!
I'm not at my computer (because vacation) but what makes this better than having a plain string with comma sep values? For the power function I could also pass in "10,2" to have the function calculate 10^2. In both cases knowledge of what ispassed into the function is needed in both caller and callee.?Additionally, (again, I cannot test it right now) is it possible to pass an object to a function? If so, wouldn't it be just as handy to pass the object? One could "new" the object in the call and have methods to add the parameters. If the methods would return themselves as object, one could also concatenate "add" calls, something like this:message calcPower? ?( new args() ? ? ? ? :addInteger("A", 10) ? ? ? ? :addInteger("B",2) ? ? ) view-as alert-box info