Recent

Author Topic: Custom type for Nil  (Read 4078 times)

Warfley

  • Hero Member
  • *****
  • Posts: 914
Custom type for Nil
« on: March 11, 2022, 02:09:07 pm »
Nil is a special value in Pascal, while it is commonly referred to as a pointer like in the wiki article: https://wiki.freepascal.org/Nil
Quote
The reserved word nil represents the special value of a pointer variable not pointing anywhere in particular.  In FPC it is implemented as pointer(0)
It is actually much more than that:
Code: Pascal  [Select][+][-]
  1.   sl := nil; // Works
  2.   sl := pointer(0); // Compiler error
So there is some compilermagic at work, to make nil compatible with different types, to which regular pointers are not compatible.

So I was thinking, why could that not be opend to the programmers to also use, by making nil it's own type, e.g. TNil. This type would than through compilermagic, be implicetly convertable to all the use-cases of nil (which already is using compiler magic to be compatible in these cases),  but would allow to use nil for custom types through strong typechecking:
Code: Pascal  [Select][+][-]
  1. type
  2.   TNullable<T> = record // see unit nullable
  3.     ...
  4.     class operator :=(None: TNil): TNullable<T>;
  5.   end;
  6.  
  7. class operator TNullable<T>.:=(None: TNil): TNullable<T>;
  8. begin
  9.   Result.Clear;
  10. end;
  11.  
  12. var
  13.   opt: TNullable<Integer>;
  14. begin
  15.   if Condition then
  16.     opt := 42
  17.   else
  18.     opt := Nil;
  19. end;
This would allow to use nil in more situations where it could be useful, without introducing any runtime overhead (such as accepting pointers, but having to do a runtime check if that pointer is actually nil).

This should also not break any existing code, as nil does currently not have a specific type anyway, and can be used as pointer, class reference, array. It would just offload the compilermagic that allows for that to a specialized type

MarkMLl

  • Hero Member
  • *****
  • Posts: 5146
Re: Custom type for Nil
« Reply #1 on: March 11, 2022, 02:37:24 pm »
Irrespective of considerations of elegance and symmetry, of far more immediate use would be some way of saying "this var parameter is nil" since it would eliminate a very large number of cases where a C-style  *char  etc. has to be preserved as a  PByte  when translated to Pascal at the expense of comprehensive error checking.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1144
    • Lebeau Software
Re: Custom type for Nil
« Reply #2 on: March 11, 2022, 06:20:59 pm »
So I was thinking, why could that not be opend to the programmers to also use, by making nil it's own type, e.g. TNil. This type would than through compilermagic, be implicetly convertable to all the use-cases of nil (which already is using compiler magic to be compatible in these cases),  but would allow to use nil for custom types through strong typechecking

This is exactly what C++ did when C++11 introduced the nullptr constant (type nullptr_t) into the language, thus allowing users to overload constructors, assignment operators, etc to accept nullptr as input separate from other types, while allowing nullptr to be implicitly convertible to all pointer types, etc.
« Last Edit: March 11, 2022, 06:22:57 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

ASerge

  • Hero Member
  • *****
  • Posts: 1978
Re: Custom type for Nil
« Reply #3 on: March 11, 2022, 06:31:25 pm »
Quote
The reserved word nil represents the special value of a pointer variable not pointing anywhere in particular.  In FPC it is implemented as pointer(0)
It is actually much more than that:
Code: Pascal  [Select][+][-]
  1.   sl := nil; // Works
  2.   sl := pointer(0); // Compiler error
Full code please. I want to see the error.

Warfley

  • Hero Member
  • *****
  • Posts: 914
Re: Custom type for Nil
« Reply #4 on: March 11, 2022, 06:45:11 pm »
Ah sorry, sl is of type TStringList:
Code: Pascal  [Select][+][-]
  1. var
  2.   sl: TStringList;
  3. begin
  4.   sl := nil;
  5.   sl := Pointer(0);
  6. end;

MarkMLl

  • Hero Member
  • *****
  • Posts: 5146
Re: Custom type for Nil
« Reply #5 on: March 11, 2022, 07:39:52 pm »
Ah sorry, sl is of type TStringList:
Code: Pascal  [Select][+][-]
  1. var
  2.   sl: TStringList;
  3. begin
  4.   sl := nil;
  5.   sl := Pointer(0);
  6. end;

But you also get an error there if you try to assign an object to it, specifically:

Code: Text  [Select][+][-]
  1. signalstore.pas(151,22) Error: Incompatible types: got "TObject" expected "TStringList"
  2.  

You can, OTOH, do this:

Code: Pascal  [Select][+][-]
  1. sl := TStringList(Pointer(0));
  2.  

Or this:

Code: Pascal  [Select][+][-]
  1. sl := TStringList(TObject(Pointer(0)));
  2.  

So your example is far more about overlaying assignment to a class instance than it is about nil, and might even be entangled with the special behaviour of instances in that they're implicitly dereferenced (which is, obviously, compiler magic of the sort you're uneasy about).

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

PascalDragon

  • Hero Member
  • *****
  • Posts: 4549
  • Compiler Developer
Re: Custom type for Nil
« Reply #6 on: March 12, 2022, 06:33:56 pm »
So I was thinking, why could that not be opend to the programmers to also use, by making nil it's own type, e.g. TNil. This type would than through compilermagic, be implicetly convertable to all the use-cases of nil (which already is using compiler magic to be compatible in these cases),  but would allow to use nil for custom types through strong typechecking:

This was already rejected four weeks ago.

Warfley

  • Hero Member
  • *****
  • Posts: 914
Re: Custom type for Nil
« Reply #7 on: March 12, 2022, 07:46:36 pm »
But the reasoning in this issue does only consider nil as function return type, with the last comment being:
Quote
Adding a special kind of type that can only be used for function result variables
And yes, if this type would only be usable for return values, I would completely agree. But I argue that there are loads of valid use-cases, when you can use this as a parameter type for overloaded functions and operators

Not only with the example of nullable from my original post. Take dynamic arrays. You cann assign nil to a dynamic array because of compiler magic, but if you want to create your own dynamic array type, e.g. using managed records, you can't make them nil assignable. This brings this weird disconnect where you the compiler provides features that you could closely emulate, but not fully, because the compiler maigc won't let you. Like if you wanted to extend the dynamic types, string and array, with a dynamic dictionairy or set type, you simply can't because the compiler won't let you create the same features, it allows in other places.
Other use-cases would be smart pointers, any other datastructures that have an "empty" state. The concept of signalizing emptyness at compiletime can be really usefull, not just for assignments, but also for function calls (e.g. for optimisation, where when you cann a function with nil as parameter, the compiler will chose a different function such that you do not have to dispatch at runtime)

MarkMLl

  • Hero Member
  • *****
  • Posts: 5146
Re: Custom type for Nil
« Reply #8 on: March 13, 2022, 12:13:39 pm »
But I argue that there are loads of valid use-cases, when you can use this as a parameter type for overloaded functions and operators

Untested code for illustration purposes:

Code: Pascal  [Select][+][-]
  1. type
  2.   TNonTrivialRecord= ...
  3.  
  4. function something(var ntr1: TNonTrivialRecord; var ntr2: TNonTrivialRecord): boolean;
  5.  
  6. function something(nntr: TNil; var ntr2: TNonTrivialRecord): boolean;
  7.  

Assuming that something() corresponds to a C function being translated that expects two pointers to TNonTrivialRecord as parameters, this allows the Pascal compiler to apply full type checking etc. when it's called. In C, a null pointer could be passed if one of the parameters was to be skipped (e.g. it provided optional initialisation state), the above allows nil to be passed as the first parameter without overlaying the function to expose pointer parameters.

As such, having a TNil type even if it can only express a single value would appear to be valuable.

Alternatively FPC could arguably benefit from a way of nilling the pointer that implements a var parameter and testing whether a var parameter is nilled: the obvious usage of nil^ and Assigned() doesn't work.

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

PascalDragon

  • Hero Member
  • *****
  • Posts: 4549
  • Compiler Developer
Re: Custom type for Nil
« Reply #9 on: March 14, 2022, 01:49:14 pm »
Not only with the example of nullable from my original post. Take dynamic arrays. You cann assign nil to a dynamic array because of compiler magic, but if you want to create your own dynamic array type, e.g. using managed records, you can't make them nil assignable. This brings this weird disconnect where you the compiler provides features that you could closely emulate, but not fully, because the compiler maigc won't let you. Like if you wanted to extend the dynamic types, string and array, with a dynamic dictionairy or set type, you simply can't because the compiler won't let you create the same features, it allows in other places.

And so what? You can't fully duplicate Writeln either.

We simply are not interested in introducing some kind of Nil-type.

Alternatively FPC could arguably benefit from a way of nilling the pointer that implements a var parameter and testing whether a var parameter is nilled: the obvious usage of nil^ and Assigned() doesn't work.

One only needs to know how to do it:

Code: Pascal  [Select][+][-]
  1. procedure Test(var aArg: LongInt);
  2. begin
  3.   Writeln(HexStr(@aArg));
  4. end;
  5.  
  6. var
  7.   i: LongInt;
  8. begin
  9.   Test(i);
  10.   Test(PLongInt(Nil)^);
  11. end.

Warfley

  • Hero Member
  • *****
  • Posts: 914
Re: Custom type for Nil
« Reply #10 on: March 14, 2022, 03:48:18 pm »
And so what? You can't fully duplicate Writeln either.
Which I think is equally as bad. I think specifically bending the rules for a single use case, but is not usable in other, similar use-cases, is simply bad design.
If some functions, like WriteLn benefit from a special feature (varadic compile time arguments with different types in this case), you aknowledge that there are use-cases for this that are worth creating a special rule for. But by only having this rule apply to a single function (WriteLn and Write in this case), you simultaniously say that only one of the use-cases is worthy to use this.
Isn't this kinda a contradiction? Saying on the one thing that this is so important it needs special handling, but not important enough to let others use it as well

It's one of the things I most like about Haskell, it is basically just a really basic set of language rules, which are used for creating a standard library, which provides everything. Even the concept of Integers in haskell is defined within the confines of the Language itself (and one of the first lections when you are learing haskell is usually to create your own integer type).
In Pascal this would not be possible, if you want to recreate what the system unit provides, either to extend it to other domains that are currently not covered by the system unit (to stick with the writeln example, e.g. to provide a writeln like functionality for TStream), or simply to understand how such things work internally, you will certainly hit a point where you just can't because the way it is used in the system unit is through compiler magic that you have no access to.
You do not need to go as far as with Haskell, where everything is Haskell, but you can also look at C++, which still has some compiler magic, but where you can also recreate all of the standard library only with C++ code. There are no magical functions like writeln or types like arrays or strings. Also in Python, while there is still some magic behind the scenes, one of the major goals of python 3 (which broke backwards compatibility with python 2) was to have less special cases and unify the systems.

There is nothing more frustrating than trying to understand and recreate a concept just to be told: Well sure we can do that, but you can't

One only needs to know how to do it:

Code: Pascal  [Select][+][-]
  1. procedure Test(var aArg: LongInt);
  2. begin
  3.   Writeln(HexStr(@aArg));
  4. end;
  5.  
  6. var
  7.   i: LongInt;
  8. begin
  9.   Test(i);
  10.   Test(PLongInt(Nil)^);
  11. end.
Ignoring the obvious problem that dereferencing a nil pointer is raising all the red flags and is something you probably never want in your code, such a thing would still require a compile time branch:
Code: Pascal  [Select][+][-]
  1. procedure DoSomethingWithNil;
  2. begin
  3.   WriteLn('Nil was passed');
  4. end;
  5.  
  6. procedure DoSomethingWith(var AValue: Integer);
  7. begin
  8.   if not Assigned(@AValue) then
  9.   begin
  10.     DoSomethingWithNil;
  11.     Exit;
  12.   end;
  13.   WriteLn(AValue, ' was passed');
  14. end;
  15.  
  16. procedure Test;
  17. var
  18.   i: Integer;
  19. begin
  20.   DoSomethingWith(PInteger(Nil)^);
  21.   DoSomethingWith(i);
  22. end;
And while this could easiely be optmised away by static analysis, the FPC is simply not good enough at optimisation to do so (this code on -O3 on FPC 3.2.2 for Linux does not optimise the "DoSomethingWith(PInteger(Nil)^);" to "DoSomethingWithNil")

MarkMLl

  • Hero Member
  • *****
  • Posts: 5146
Re: Custom type for Nil
« Reply #11 on: March 14, 2022, 04:38:47 pm »
If some functions, like WriteLn benefit from a special feature (varadic compile time arguments with different types in this case), you aknowledge that there are use-cases for this that are worth creating a special rule for.

WriteLn() etc. are very much edge cases, since (a) they've been present since the earliest versions of the language and (b) in those days very few platforms supported linkable libraries and having predefined procedures supported directly by the compiler was pretty standard.

As such, I think the emphasis was more on "can the compiler work out how to handle these actual parameters?" rather than "this is the formal parameter list, to which everything must conform".

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

MarkMLl

  • Hero Member
  • *****
  • Posts: 5146
Re: Custom type for Nil
« Reply #12 on: March 14, 2022, 04:41:58 pm »
Code: Pascal  [Select][+][-]
  1. procedure Test(var aArg: LongInt);
  2. begin
  3.   Writeln(HexStr(@aArg));
  4. end;
  5.  
  6. var
  7.   i: LongInt;
  8. begin
  9.   Test(i);
  10.   Test(PLongInt(Nil)^);
  11. end.

I see, I'd been trying (nil^). Is it possible to test for a var parameter that's been fabricated like that being nil?

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

Warfley

  • Hero Member
  • *****
  • Posts: 914
Re: Custom type for Nil
« Reply #13 on: March 14, 2022, 05:01:30 pm »
As such, I think the emphasis was more on "can the compiler work out how to handle these actual parameters?" rather than "this is the formal parameter list, to which everything must conform".
Yeah, but wouldn't it be really useful to make use of such a feature yourself? C++ allows exactly that through varadic template parameters. They are pretty complicated to deal with if you want to implement functions using them yourself, so I can understand if people do not want to use them, but it shows that something like this is possible. And for calling functions using them, you do not need to know that they exist, so from a usage point it is basically the same as compiler magic, but with the difference that if you really want to, you can make this yourself.

That said, WriteLn can do other stuff that is exclusive to the Write function like:
Code: Pascal  [Select][+][-]
  1. var
  2.   i: Integer;
  3. begin
  4.   WriteLn(i:3);
Which btw, I would also find really cool if you could use this yourself, either by allowing to specify optional arguments for parameters, or by making : an overloadable operator.

I think writeln is a really cool function, it's a shame as a programmer you will never be able to create something as cool as that
« Last Edit: March 14, 2022, 05:03:17 pm by Warfley »

440bx

  • Hero Member
  • *****
  • Posts: 3021
Re: Custom type for Nil
« Reply #14 on: March 14, 2022, 07:29:53 pm »
I think writeln is a really cool function, it's a shame as a programmer you will never be able to create something as cool as that
It isn't necessarily a shame.  Anyone who is familiar with C and its handling of "..." to pass a variable number of parameters is keenly aware of all the the problems that feature opens the door to.

Since you mentioned function overloading, I routinely use overloading on cdecl functions that take a variable number of parameters to ensure that there is an overload for whatever use I've made of the function.  I other words, I normally don't use the "root" function that accepts a variable number of parameters, instead I use the overloads which use a fixed number of parameters, each with a declared parameter type.

For instance, overload printf with the parameter signatures of each use.  That way, I get the convenience of a variable number of parameters while not losing the compiler's type checking.  That said, it is the programmer's responsibility to ensure that, in the implementation, the type descriptors used match the parameters in every case but, at least, there is only _one_ place where that critical piece needs to be done.  Which is what the compiler does for you when using writeln (and similar functions.)

Summary and conclusion: the problem with variable number of parameters is matching the parameter to the correct type and, the C implementation is, by any meausre, very inadequate (which is typical of C.)  Using function overloading comes very close to enabling the programmer to write variable count parameter functions _almost_ ideally.



FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

 

TinyPortal © 2005-2018