Recent

Author Topic: null for a var parameter  (Read 1076 times)

mtanner

  • Full Member
  • ***
  • Posts: 241
null for a var parameter
« on: July 07, 2020, 11:46:05 pm »
I read somewhere that in GNU-Pascal you cal, when calling a subroutine, use "null" instead of an actual variab;e name. Does Lazarus/FP support his? I have some routines that return values in a number of var parameters, and the routines that call them don't allways need all the values, so using "null" would save declaring extraneous variables.

And out of interest does anyone know whether C/C++ supports null parameters?

jamie

  • Hero Member
  • *****
  • Posts: 3537
Re: null for a var parameter
« Reply #1 on: July 08, 2020, 12:40:03 am »
You must be thinking of Pointers because in languages like C/C++ functions that expect a pointer to an object can receive a NULL pointer which is 0 address. In Fpc/pascal this would be a NIL.

  However, what you are looking for is overloading of functions and the use of default values

 function Name(Aparamater:Integer; ASecond:Integer=1; AThird:Integer=2);

  Name(1);
  Name(1,3);
 
or NAME(1,3,4);

 All produce the same call in the background but the compiler uses the defaults for values you don't fill in.
  the Defaults being the =? after the type

The only true wisdom is knowing you know nothing

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 813
    • Lebeau Software
Re: null for a var parameter
« Reply #2 on: July 08, 2020, 12:41:43 am »
In Delphi and FreePascal, Null is a Variant, that would hardly be appropriate to pass to a var parameter that is not expecting a Variant variable.  Perhaps you are thinking of nil instead?  In Delphi at least (not sure about FreePascal), it is possible to pass nil to a var parameter, but it does require a type-cast (the codegen is handled correctly, though), eg:

Code: Pascal  [Select][+][-]
  1. procedure Test(var Value: Integer);
  2. begin
  3.   if @Value <> nil then
  4.     Value := 12345;
  5. end;
  6.  
  7. Test(PInteger(nil)^); // or Test(Integer(nil^));
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

winni

  • Hero Member
  • *****
  • Posts: 1813
Re: null for a var parameter
« Reply #3 on: July 08, 2020, 01:21:55 am »
Hi!

It is allowed in

{$mode objfpc}

Winni

Warfley

  • Sr. Member
  • ****
  • Posts: 303
Re: null for a var parameter
« Reply #4 on: July 08, 2020, 01:44:37 am »
May I note that this is an absolutely terrible idea. First the syntax is aweful (I mean you are literally dereferencing a nil-pointer, this hurts to look at), but simply from an understandability point of view, if you see a function taking a var parameter, you would not assume that it is nullable. Similarly, if you write a function that takes a var argument, you usually don't assume that someone will put nil in it (I have never ever in any of my code checked if a var parameter is nil).

If you want to have a nullable argument, for your own sanity and (in case you work with others) for your teamates sanity, I recommend using a pointer, because this already signalises by simply the fact that it is a pointer, that it is nullable. If I get a pointer I automatically know, it might be nil, because otherwise I would have used a var parameter.

Other languages solved this way better, like C++ with references and the optional type, or swift with it's language level optional modifiers. But for Pascal, better stick with the types that are well known for being nullable. Otherwise, over short or long this will be a disaster.

mtanner

  • Full Member
  • ***
  • Posts: 241
Re: null for a var parameter
« Reply #5 on: July 08, 2020, 03:02:29 pm »
Thank you all for the comments and suggestions.

Just to explain my thinking. The functions I am concerned with calculate a number of values for a scenario, lets call them A1,A2,A3, all declared as var parameters, and used to return values from the function (not supply values to the function).So something like
procedure SubCalc(X,Y,Z:double;var A1,A2,A3:double);
 SubCalc may be called by several other functions, not all of them needing A1,A2,A3. One caller function may, for example, only need the A2 value, but still has to declare and supply variables for A1 and A2, introducing extraneous clutter in the calling function.

I could do this just by making A1,A2,A3 pointers, and have the function check for nil before putting a value in the "variable". In fact I may do this. The calling function can then use nil for values not of interest.

I don't want to use overloaded version of SubCalc, because SubCalc is often a tricky mathematically complex thing, and overloaded functions mean duplicating the code - and that seems a possible source of bugs when the function later gets modified.  I could use overloaded wrapper functions around SubCalc, so just one copy of the central code, but that's not very elegant. The number of overloaded or wrapper functions could be a bit large, making for ugly, error-prone code.

To be honest, I am a little wary of overloaded functions, makes me nervous to have different functions with the same name - but that is just my personal preference. I'm happy for system supplied functions, like IntToStr, to be multiple overloaded functuions, but I don't like writing them myself.

Another possibility I considered was to have all the return values is a simple record.

It's not a huge deal, just having come across the idea on the web seemd like a neat way of reducing code complexity. The reduction in cide complecity would be fairly minor, so it's not worth too much agonising.

On a historical note. many years ago I wrote some serious scientific applications in the PL/I language from IBM. These days we are used to strict data-type checking, which I think is great coding-time bug preventer. PL/I had the opposte view, if you tried to assign, say, a string to an integer variable, then PL/I would helfully insert a call to a conversion routine (which would usually fail at run time if the string did not contain an integer because you coded a bug). So you'd have to check the compiler report which listed where conversion routines had been inserted, then go and fix the code. On the other hand PL/I did have an interesting version of overloaded function. A procedure could have multiple "entry points" of procedure headers, each with a different list of parameters. This worked quite neatly, as only one copy of the core code was needed.

One little thing I wish Pascal had copied from PL/I is that in PL/I you could write
 "If A=B then Return(X)'"
which was equivalent to the Pascal
" If A=B then begin Result:=X; Exit; end".
The Pascal version just looks verbose compared to the PL/I.

bytebites

  • Sr. Member
  • ****
  • Posts: 299
Re: null for a var parameter
« Reply #6 on: July 08, 2020, 03:06:43 pm »
Pascal is less verbose
Code: Pascal  [Select][+][-]
  1. If A=B then exit(X)

mtanner

  • Full Member
  • ***
  • Posts: 241
Re: null for a var parameter
« Reply #7 on: July 08, 2020, 03:25:37 pm »
Didn't know you could write Exit(X), handy to learn, thanks.

Bart

  • Hero Member
  • *****
  • Posts: 3911
    • Bart en Mariska's Webstek
Re: null for a var parameter
« Reply #8 on: July 08, 2020, 04:15:06 pm »
I could do this just by making A1,A2,A3 pointers, and have the function check for nil before putting a value in the "variable".

You still have to de clare them as variables in the calling routine, so this solves nothing.

I don't want to use overloaded version of SubCalc, because SubCalc is often a tricky mathematically complex thing, and overloaded functions mean duplicating the code - and that seems a possible source of bugs when the function later gets modified.

This may or may not be applicable.

Code: Pascal  [Select][+][-]
  1. procedure SubCalc(X,Y,Z:double;var A1,A2,A3:double);
  2. begin
  3.    ...
  4. end;
  5.  
  6. procedure SubCalc(X,Y,Z:double;var A1,A2:double); //user does not need A3 and does not need to declare it
  7. var
  8.   dummyA3: double;
  9. begin
  10.    SubCalc(X,Y,Z:double;var A1,A2,dummyA3:double);
  11. end;
  12.  
  13. procedure SubCalc(X,Y,Z:double;var A1:double); //user does not need A2 and A3
  14. var
  15.   dummyA2, dummyA3: double;
  16. begin
  17.    SubCalc(X,Y,Z:double;var A1,dummyA2,dummyA3:double);
  18. end;

All overloads simply call the original SubCalc, so no code duplication, no copy/paste errors.
If there is a bug in SubCalc you only have to change it in 1 place.

Obviously if you alos have a need to call SubCalc and discard A1 and A3, you cannot solve it this way (since A1,A2 and A3 are of the same type).
Also: you have to make sure the A1..A3 parameters have meaningfull names (so CodeTools will give helpfull hints when writing your code).

Bart

mtanner

  • Full Member
  • ***
  • Posts: 241
Re: null for a var parameter
« Reply #9 on: July 08, 2020, 04:35:05 pm »
Bart, if I had a calling function that didi not require, say, A2, then I could code
   SubCalc(X,Y,X,A1,nil,A3)

and not have to declare an A2 in the calling function, which was my original hope.

I guess what I was hoping for was some kind of "dustbin" variable so I copuld code
  SubCalc(X,Y,Z,A1,dustbin,A3)
where as far as Subcalc was concerned it wa being provided with a variable in which to place a value. I could of course do this with a global junk variable
var Dnil:double; { somewhere in a commmon unit }
  SubCalc(X,Y,Z,A1,Dnil,A3)

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 8731
  • FPC developer.
Re: null for a var parameter
« Reply #10 on: July 08, 2020, 04:41:10 pm »
One can use default parameters, but they must be at the end, and don't work for VAR parameters anyway.

wp

  • Hero Member
  • *****
  • Posts: 7551
Re: null for a var parameter
« Reply #11 on: July 08, 2020, 05:11:20 pm »
Bart, if I had a calling function that didi not require, say, A2, then I could code
   SubCalc(X,Y,X,A1,nil,A3)

and not have to declare an A2 in the calling function, which was my original hope.
This is what Bart was trying to show you in the second procedure. You can "overload" procedures which have the same name but different parameter lists. Bart declars two SubCalc procedures, one with two and another one with three of the A* parameters.
Mainly Lazarus trunk / fpc 3.2.0 / all 32-bit on Win-10, but many more...

Warfley

  • Sr. Member
  • ****
  • Posts: 303
Re: null for a var parameter
« Reply #12 on: July 08, 2020, 05:12:30 pm »
I would do it completely differently:
Code: Pascal  [Select][+][-]
  1. type
  2. TSubCalcResult = record
  3.   A1, A2, A3: double;
  4. end;
  5.  
  6. function SubCalc(...): TSubCalcResult;
  7.  
  8. // usage for only A1:
  9. A1 := SubCalc(...).A1;
  10. // usage for more than one A:
  11. subcalcRes := SubCalc(...);
  12. // now use subcalcRes.A1, subcalcRes.A2, subcalcRes.A3, as much as you like, what you don't need simply ignore

IMHO are var paremters a form of bad coding style. They can be used to slightly increase performance in some cases and sometimes they are just convinient (e.g. for recursion), but most of the time you don't need that, and having an explicit return parameter makes it often clearer what the function is actually doing.

Since fpc 3.2 i've wrote the following two units, with which I can replace most usages of out or var parameters:
Code: Pascal  [Select][+][-]
  1. unit recutils.Optional;
  2.  
  3. {$mode Delphi}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. interface
  7.  
  8. uses
  9.   SysUtils, Memory.NilPointer;
  10.  
  11. type
  12.  
  13.   ENoValueFoundException = class(Exception);
  14.  
  15.   TEmptyOptional = TNilPointer;
  16.  
  17.   { TOptional }
  18.  
  19.   {$Hints Off}
  20.   TOptional<T> = record
  21.   private
  22.     FHasValue: Boolean;
  23.     FValue: T;
  24.  
  25.     class operator Initialize(var a: TOptional<T>); {$IFDEF INLINING}inline;{$ENDIF}
  26.   public
  27.     function Get: T; {$IFDEF INLINING}inline;{$ENDIF}
  28.     function GetOrDefault(const ADefault: T): T; {$IFDEF INLINING}inline;{$ENDIF}
  29.     function HasValue: Boolean; {$IFDEF INLINING}inline;{$ENDIF}
  30.     constructor Create(constref AValue: T);
  31.  
  32.     class operator Implicit(constref AValue: T): TOptional<T>; {$IFDEF INLINING}inline;{$ENDIF}
  33.     class operator Explicit(constref AValue: T): TOptional<T>; {$IFDEF INLINING}inline;{$ENDIF}
  34.     class operator Implicit(constref AValue: TNilPointer): TOptional<T>; {$IFDEF INLINING}inline;{$ENDIF}
  35.     class operator Explicit(constref AValue: TNilPointer): TOptional<T>; {$IFDEF INLINING}inline;{$ENDIF}
  36.     class operator Implicit(constref opt: TOptional<T>): Boolean; {$IFDEF INLINING}inline;{$ENDIF}
  37.     class operator Explicit(constref opt: TOptional<T>): Boolean; {$IFDEF INLINING}inline;{$ENDIF}
  38.     class operator LogicalNot(constref opt: TOptional<T>): Boolean; {$IFDEF INLINING}inline;{$ENDIF}
  39.  
  40.     class function Empty: TOptional<T>; {$IFDEF INLINING}inline;{$ENDIF} static;
  41.   end;
  42.  
  43. function EmptyOptional: TEmptyOptional;
  44.  
  45. implementation
  46.  
  47. { TOptional }
  48.  
  49. class operator TOptional<T>.Initialize(var a: TOptional<T>);
  50. begin
  51.   a.FHasValue := False;
  52. end;
  53.  
  54. function TOptional<T>.Get: T;
  55. begin
  56.   if not FHasValue then
  57.     raise ENoValueFoundException.Create('Optional does not contain any value');
  58.   Result := FValue;
  59. end;
  60.  
  61. function TOptional<T>.GetOrDefault(const ADefault: T): T;
  62. begin
  63.   if not FHasValue then
  64.     Result := ADefault
  65.   else
  66.     Result := FValue;
  67. end;
  68.  
  69. function TOptional<T>.HasValue: Boolean;
  70. begin
  71.   Result := FHasValue;
  72. end;
  73.  
  74. constructor TOptional<T>.Create(constref AValue: T);
  75. begin
  76.   FHasValue := True;
  77.   FValue := AValue;
  78. end;
  79.  
  80. class operator TOptional<T>.Implicit(constref AValue: T): TOptional<T>;
  81. begin
  82.   Result := TOptional<T>.Create(AValue);
  83. end;
  84.  
  85. class operator TOptional<T>.Explicit(constref AValue: T): TOptional<T>;
  86. begin
  87.   Result := TOptional<T>.Create(AValue);
  88. end;
  89.  
  90. class operator TOptional<T>.Implicit(constref AValue: TNilPointer): TOptional<T>;
  91. begin
  92.   Result := TOptional<T>.Empty;
  93. end;
  94.  
  95. class operator TOptional<T>.Explicit(constref AValue: TNilPointer): TOptional<T>;
  96. begin
  97.   Result := TOptional<T>.Empty;
  98. end;
  99.  
  100. class operator TOptional<T>.Implicit(constref opt: TOptional<T>): Boolean;
  101. begin
  102.   Result := opt.HasValue;
  103. end;
  104.  
  105. class operator TOptional<T>.Explicit(constref opt: TOptional<T>): Boolean;
  106. begin
  107.   Result := opt.HasValue;
  108. end;
  109.  
  110. class operator TOptional<T>.LogicalNot(constref opt: TOptional<T>): Boolean;
  111. begin
  112.   Result := not opt.HasValue;
  113. end;
  114.  
  115. class function TOptional<T>.Empty: TOptional<T>;
  116. begin
  117.   Result.FHasValue := False;
  118.   Result.FValue := Default(T);
  119. end;
  120.  
  121. function EmptyOptional: TEmptyOptional;
  122. begin
  123.   Result := nilptr;
  124. end;
  125.  
  126. end.
  127.  
Example:
Code: Pascal  [Select][+][-]
  1. function TryStrToInt(str: String): TOptional<Integer>;
  2. var
  3.   res: Integer;
  4. begin
  5.   if TryIntToStr(str, res) then
  6.     Result := res
  7.   else
  8.    Result := EmptyOptional;
  9. end;
  10.  
  11. // ...
  12. function isInteger(str: String): Boolean;
  13. begin
  14.   Result := TryIntToStr(str);
  15. end;
  16.  
  17. procedure foobar(bar: String);
  18. var
  19.   foo: TOptional<Integer>;
  20. begin
  21.   foo := TryStrToInt(bar);
  22.   if (foo) then
  23.     // do something with foo.get
  24.   else
  25.     // Not a valid number
  26. end;

And, for having multiple output variables:
Code: Pascal  [Select][+][-]
  1. unit recutils.Tuple;
  2.  
  3. {$mode Delphi}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. interface
  7. type
  8.  
  9.   { TPair }
  10.  
  11.   TPair<T, U> = record
  12.     first: T;
  13.     second: U;
  14.     constructor Create(first: T; second: U);
  15.   end;    
  16.  
  17.   { TPair }
  18.  
  19.   { TTriple }
  20.  
  21.   TTriple<T, U, W> = record
  22.     first: T;
  23.     second: U;
  24.     third: W;
  25.     constructor Create(first: T; second: U; third: W);
  26.   end;
  27.  
  28. implementation
  29.  
  30. { TPair }
  31.  
  32. constructor TPair<T, U>.Create(first: T; second: U);
  33. begin
  34.   self.first := first;
  35.   self.second := second;
  36. end;
  37.  
  38. { TTriple }
  39.  
  40. constructor TTriple<T, U, W>.Create(first: T; second: U; third: W);
  41. begin
  42.   self.first := first;
  43.   self.second := second;
  44.   self.third := third;
  45. end;
  46.  
  47. end.
  48.  

In your case this would be:
Code: Pascal  [Select][+][-]
  1. function SubCalc(...): TTriple<double, double, double>;

I've found that about 90% of all cases you need more than one output a double or triple is enough, if I need more, I write a custom type for it
« Last Edit: July 08, 2020, 05:14:38 pm by Warfley »

Bart

  • Hero Member
  • *****
  • Posts: 3911
    • Bart en Mariska's Webstek
Re: null for a var parameter
« Reply #13 on: July 08, 2020, 10:57:23 pm »
Bart, if I had a calling function that didi not require, say, A2, then I could code
   SubCalc(X,Y,X,A1,nil,A3)

No, nil is a constant and you have declared A2 to be a var parameter, hence you must declare it as such in the calling procedure:

Code: Pascal  [Select][+][-]
  1. procedure SubCalc(X,Y,Z:double;var A1,A2,A3:double);
  2. begin
  3.    ...
  4. end;
  5.  
  6. ...
  7. var
  8.   X,Y,Z,A1, A3: double;
  9. begin
  10.   ...
  11.   SubCalc(X,Y,X,A1,nil,A3); //this will not compile
  12.   ...
  13. end.

Bart

jamie

  • Hero Member
  • *****
  • Posts: 3537
Re: null for a var parameter
« Reply #14 on: July 08, 2020, 11:49:26 pm »
outside of using overloads and depending on how they are implemented could still lead to a lot of speed bloat...

 May I suggest that a worker record with all the fields in it be past as a minimum base to a function with a function control variable ..


Procedure SubCalc(Var TheRecord:TTheRecord; TheFunctionCode:TFuncCode);

 The record contains all the possible fields to be used
the Function Code contains the action to take on it.
etc.

Actions could also involve multiple actions so make it a SET



The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018