Recent

Poll

I would like to see this feature added to the compiler

Yes
8 (34.8%)
No
10 (43.5%)
I don't care
5 (21.7%)

Total Members Voted: 23

Author Topic: Feature request: hard type creation/declaration  (Read 10164 times)

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12712
  • FPC developer.
Re: Feature request: hard type creation/declaration
« Reply #45 on: July 08, 2025, 11:38:49 am »
That concept is called an opaque type and some languages like Swift or Haskell have them. It's a type the internals of which you do not know about or at best can make some assumptions about (like that it overloades the = and <> operators). C has them also for structs/records but only allows to access them via pointer.

IMHO opague adds another level that also prohibits you to mess with the internals of the handle type (and you can go pretty far with that, even disallowing casting). Preventing assignment compatibility is a less strong feature, but is IMHO the more desirable subset that is needed. The bit that prevent most of the mistakes so to say. Additional layers of information hiding have diminishing returns.

Quote
What handles would need would be some information about the memory layout but no information about the interface. Basically just a "Binary blob" type. Which would technically be possible to implement, but why not just use:
Code: Pascal  [Select][+][-]
  1. TMyHandle = record data: array[0..HANDLESIZE-1] of Byte end;

Careful. Rules for parameter passing a structured type and an ordinal type might be different for some architectures. For values smaller than the word size, record packing/padding might also be a problem.

Quote
So I do not see why you would need to introduce a new kind of type to the language.

I think it would be a good thing to have a modeswitch that forces Type x = type Y; to be not assignment compatible. But that might require an extra flag in the type system and thus non trivial work. But it would be the less hackish way of doing it.

Ergo, this can only be done in languages where "everything is an object".

I don't think so, and even then very few are pure in that regard (but rely on autoboxing of native types)
« Last Edit: July 08, 2025, 11:51:47 am by marcov »

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #46 on: July 08, 2025, 12:00:39 pm »
Because (as far as I understand) he feels that such a type is needed commonly enough to simplify the definition.

And also so that certain operators should automatically already exists. So it is not needed to add your own unit of 10 to 30 lines to define all the needed ops.

Thats actually not that difficult either with generics:
Code: Pascal  [Select][+][-]
  1. generic TBinaryBlob<const Size: SizeInt> = packed record
  2.   strict private data: array[0..Size-1] of Byte;
  3.   class operator =(...): Boolean;
  4.   class operator <>(...): Boolean;
  5.   class operator Explicit(...): specialize TBinaryBlob<Size>;
  6.   ...
  7. end;
It just needs to be defined once and you can create as many instances as you like of it. If this would be really necessary to be provided by the FPC this could also be put into the RTL. There could even be variants like TComparableBlob which has = and <>, OrdinalBlob which has the relational operators <, >, etc.. Just an hour or so of work and you have something you can use for the rest of your life in any code base without having to re-define it every time, no new compiler feature necessary.

That is why I was doubtful a type for what he wants would be easy to define.

Because, well, its not an opaque type. Its an inbetween. With very specific properties chosen.
The underlying problem is that Pascals Typesystem for base types is rooted in set theory, while the idea of having distinctive types where you define subtypes and specific conversion points is rooted in type theory.

This is btw something that makes Pascal somewhat unique, because most programming languages are based around type theory. It's a shame that Pascal doesn't do much with it (like this would be perfect to enable constraint languages over types). But it also means that things like the original question of this thread are non trivial to put into practice because it's a completely different underlying theory.
Type theory and set theory are two perfectly valid theories but they are two different ways to model a system. So mixing them is just not really trivial

440bx

  • Hero Member
  • *****
  • Posts: 6148
Re: Feature request: hard type creation/declaration
« Reply #47 on: July 08, 2025, 12:09:48 pm »
Back to the topic.

Out of interest.... If that feature existed, may I assume you would want to have (or do your own) definition of Windows API calls? So that e.g. "setwindowpos" can not be called with anything but a HWND as first argument?
Yes and, I already have my own Windows API definitions, it's not quite complete yet but, I'm in the ballpark of 10,000 definitions (including overloads which account for about 35 to 40% of that.)

And (as you stated yourself earlier), if a similar function would take special values, such as invalide_handle, then there had to be an INVALID_HWND.
Yes and, that's why it's important for the type to behave like a normal type, that is, being able to typecast constants like it's done with enumerations (or other types for that matter.)

But, and this is my question, what would you do about a function such as SelectObject?
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject

The 2nd argument can be a Bitmap, Font, or other Handle?
I'd simply create an overload for every type, which is what overloads are for.  That way if an HWND, an HMODULE or some other HWHATEVER that is not a GDI object then the compiler can reject that.

So now, you don't just need distinct types, you need types that also have some sort of inheritance / parent-type?
Not at all, all that's needed is one definition/overload per type.  Clean and simple.

Or would you rather add lots of overloaded functions?
Yes, absolutely.  That's what overloading is for and, SelectObject only needs 5 overloads, copy/paste takes care of that rather quickly.

See https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle it takes more than a dozen different types of handles.
Yes, CloseHandle takes more work but, those definitions need be done only once.  Yes, one time, CloseHandle has to be defined a dozen or more times, small price to pay for effective type checking in every program written after that.

I am not even sure that classic inheritance would solve the Windows API, there may be some types in different groups...
I don't see a problem.  My definitions have overloads galore, the compiler doesn't mind at all and, I get stronger type checking.   It's all good.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #48 on: July 08, 2025, 01:16:12 pm »
And why don't you do it with a record? You could even create an intermediary type where all of these types can cast onto but not back, so you have the ability to assign them to that common type but not between each other.
This way you don't even need multiple overloads for the function, you just need one where the typesystem handles the rest.

If that is so important to you that you spend hours in this thread, why don't you just sit down and write a generic record for this? It's like one hour you need to invest once, and you can use it for the rest of your life in any project you like. If it's really good you can even make it a merge request to the RTL and so everyone can benefit from the work you put in there.

All you have written why records are not an option for you is because you think it's to much effort to write like 50 lines of code once. Like not even for every type or every usage, just once, forever. (Well my theory is that you don't know how as was obvious when you said you had trouble to enable casts even though this is incredibly easy. But if you don't know, just ask :))
« Last Edit: July 08, 2025, 01:19:02 pm by Warfley »

440bx

  • Hero Member
  • *****
  • Posts: 6148
Re: Feature request: hard type creation/declaration
« Reply #49 on: July 08, 2025, 01:44:35 pm »
And why don't you do it with a record?
Because I have not found a way to do it, with records or anything else, that I deemed satisfactory.

This way you don't even need multiple overloads for the function, you just need one where the typesystem handles the rest.
I WANT the multiple overloads.  That serves as documentation.  For instance, in the case of SelectObject, the overloads make it crystal clear what the function can handle.

If that is so important to you that you spend hours in this thread,
I didn't spend hours in this thread and, I'm responding only to be polite. 

why don't you just sit down and write a generic record for this?
Because I want the solution to have two characteristics: 1. it must be simple.  2. it must _not_ require constant modifications and updating to accommodate new types.  IOW, it must be very straightforward, like what the double use of "type" is supposed to do, very straightforward but unfortunately that doesn't cut it.

I tell you what, if you can provide a solution that complies with the above, not only I'd be grateful, I'd be impressed (I assure you, that doesn't happen very often.)  Just to emphasize the requirements, 1 & 2 above AND overloads MUST be required to accommodate multiple types in functions, e.g, SelectObject, a solution that does not require overloads is NOT a solution.

Can you do that ?

ETA:

very important, the type must be usable to typecast constants that are assignment compatible with the _underlying_ type.  e.g, DESKTOP_WINDOW = HWND(0) presuming that HWND is now a hard type.

Just in case, it must be possible to use the value _without_ referring to whatever field name the record uses (I'm presuming you're going to use a record) IOW, if the record has a field "v" to hold the value, "v'" must _never_ need to be explicitly referenced.

« Last Edit: July 08, 2025, 01:51:43 pm by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #50 on: July 08, 2025, 01:58:33 pm »
Well there seems to actually be a bug in the compiler that prevents this from being possible, because the Explicit typecast is not transfered correctly when the type copy is created.

If this bug is fixed the following should satisfy all of your requirements:
Code: Pascal  [Select][+][-]
  1. {$ModeSwitch advancedrecords}
  2.  
  3. interface
  4.  
  5. type
  6.   generic TTypeWrapper<T> = record
  7.     data: T;
  8.  
  9.     class operator =(const lhs, rhs: specialize TTypeWrapper<T>): Boolean;inline;
  10.     class operator <>(const lhs, rhs: specialize TTypeWrapper<T>): Boolean;inline;
  11.     class operator Explicit(const rec: specialize TTypeWrapper<T>): T;inline;
  12.     class operator Explicit(const value: T): specialize TTypeWrapper<T>;inline;
  13.   end;
  14.  
  15. implementation
  16.  
  17. class operator TTypeWrapper.=(const lhs, rhs: specialize TTypeWrapper<T>): Boolean;
  18. begin
  19.   result:=lhs=rhs;
  20. end;
  21.  
  22. class operator TTypeWrapper.<>(const lhs, rhs: specialize TTypeWrapper<T>): Boolean;
  23. begin
  24.   result:=lhs<>rhs;
  25. end;
  26.  
  27. class operator TTypeWrapper.Explicit(const rec: specialize TTypeWrapper<T>): T;
  28. begin
  29.   result:=rec.data;
  30. end;
  31.  
  32. class operator TTypeWrapper.Explicit(const value: T): specialize TTypeWrapper<T
  33.   >;
  34. begin
  35.   result.data:=value;
  36. end;
Example:
Code: Pascal  [Select][+][-]
  1. type
  2.   THWND = type specialize TTypeWrapper<Integer>;
  3.   THandle = type specialize TTypeWrapper<Integer>;
  4.  
  5.  
  6. var
  7.   hw: THWND;
  8.   hnd: THandle;
  9. begin
  10.   hw := THWND(0);
  11.   hnd := THandle(-1);
  12.   WriteLn(Integer(hw));
  13.   WriteLn(Integer(hnd));
  14.   hw := 0; // Error
  15.   hnd := hw; // Error
  16. end.

The fix should also not be to difficult to do maybe I'll look into it later today

Kays

  • Hero Member
  • *****
  • Posts: 632
  • Whasup!?
    • KaiBurghardt.de
Re: Feature request: hard type creation/declaration
« Reply #51 on: July 08, 2025, 02:00:30 pm »
[…] Extended Pascal has introduced an opaque typesystem called Schemata, […]
You mean restricted data types? At any rate I endorse FPC supporting EP.
Yours Sincerely
Kai Burghardt

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #52 on: July 08, 2025, 02:25:07 pm »
No, extended Pascal also defines restricted types, but those are basically the equivalent of making the internals of the type private in object pascal. I guess it's closest to what 440bx wants, but also not fully (because they remove all operations not just making them distinct).

Schemata are different, they allow to define type interfaces. To take an example from the extpas standard:
Code: Pascal  [Select][+][-]
  1. type
  2.   variant record(d : a subrange) =
  3.     record case d of
  4.       1: (f1 : integer);
  5.       2: (f2 : integer);
  6.     end;
This would be roughly equivalent to
Code: Pascal  [Select][+][-]
  1. type
  2.   generic MyVariant<d> =
  3.     record case d of
  4.       1: (f1 : integer);
  5.       2: (f2 : integer);
  6.     end;
On a static level, but it can also be used dynamically, in which case it is like interfaces for classes in Object Pascal. Also it has full typechecking of the schema parameters.

It's extremely powerful, but also extremely complicated. I guess the reason no one implemented it back in the day was because of that complexity. That said, modern compilers can probably do much better, and this feature is mostly implemented in go and Swift.
So ExtPas was just way ahead of it's time with that

About integrating ExtPas into FPC, I'm not sure how well it works, at least if you want 100% compliance to the standard, because ExtPas is pretty consistent in it's definitions, which means all the systems build on top of each other. For example all different types of strings are unified through the string schema and string manipulation functions just take the string schema as argument.
So I guess there would be the possibility to take certain elements from ExtPas, but really full compliance would probably require some re-designs of the FPC internals.

That said, if schemata would be fully incorporated, I'd probably never have to use generics or OOP ever again, because this single concept unifies pretty much all of that into one construct.

I think a good ExtPas compiler with a few slight adjustments (e.g. UTF-8 strings, arbitrary sized integers) with would probably easily take my top spot for favorite language.
« Last Edit: July 08, 2025, 02:35:31 pm by Warfley »

440bx

  • Hero Member
  • *****
  • Posts: 6148
Re: Feature request: hard type creation/declaration
« Reply #53 on: July 08, 2025, 02:56:48 pm »
@Warfley,

Before anything, thank you for your efforts.  I appreciate them.

What you've offered looks quite good.  It's really unfortunate that there is a bug that prevents your solution from fully working but, that's a different problem.

Obviously, I'd really like to use something that fully works.  You mentioned you might have time to work on fixing the bug.  Your best guess: do you think that the fix could be part of the upcoming v3.2.4 release ?  I know that's hoping for a lot but, I _really_ like to use the compiler's release/stable version.

Anyway, if you can provide a way to "fix" v3.2.2 to make your solution operate properly, I would appreciate that as it would allow me to take your solution for a "walk" (more like a marathon... ;) )

ETA:

Fixed typo.
« Last Edit: July 08, 2025, 03:12:12 pm by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #54 on: July 08, 2025, 03:04:22 pm »
I will try to fix this based on 3.2.4 so maybe theres a chance to still get it in there (even though I doubt it).

If it works well, I can assure you it will also become an indispensable tool for me, because I also was very frustrated about not being able to re-define types for overloading (personally for me it's even more about String types, because I want to distinguish between things like passwords, usernames and stuff like that to not accidentally enter the wrong thing in the wrong parameter)

440bx

  • Hero Member
  • *****
  • Posts: 6148
Re: Feature request: hard type creation/declaration
« Reply #55 on: July 08, 2025, 03:14:17 pm »
Sounds good.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12196
  • Debugger - SynEdit - and more
    • wiki
Re: Feature request: hard type creation/declaration
« Reply #56 on: July 08, 2025, 03:16:19 pm »
Well there seems to actually be a bug in the compiler that prevents this from being possible, because the Explicit typecast is not transfered correctly when the type copy is created.

Well the problem IMHO is

Quote
Code: Pascal  [Select][+][-]
  1. type
  2.   THWND = type specialize TTypeWrapper<Integer>;
  3.   THandle = type specialize TTypeWrapper<Integer>;
  4.  


by doing an extra "type" THWND is a new type, not anymore the "specialized type". But the operator was specialized for the "specialized type". It does not work for the new distinct type created by the extra "type".

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12196
  • Debugger - SynEdit - and more
    • wiki
Re: Feature request: hard type creation/declaration
« Reply #57 on: July 08, 2025, 03:33:32 pm »
What you can do, but it is ONE EXTRA line per type.

- I hardcoded the integer, but it could be a 2nd generic param
- inside the generic, I use just the generic name as ref to the current specialization.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.   {$Mode objfpc}
  3.   {$ModeSwitch advancedrecords}
  4.  
  5.     type
  6.       generic TTypeWrapper<T> = record
  7.         data: integer;
  8.  
  9.         class operator =(const lhs, rhs: TTypeWrapper): Boolean;inline;
  10.         class operator <>(const lhs, rhs: TTypeWrapper): Boolean;inline;
  11.         class operator Explicit(const rec: TTypeWrapper): integer;inline;
  12.         class operator Explicit(const value: integer): TTypeWrapper;inline;
  13.       end;
  14.  
  15.       //generic TTypeWrapper<T> = specialize TTypeWrapper<T>;
  16.  
  17.  
  18.     class operator TTypeWrapper.=(const lhs, rhs: TTypeWrapper): Boolean;
  19.     begin
  20.       result:=lhs=rhs;
  21.     end;
  22.  
  23.     class operator TTypeWrapper.<>(const lhs, rhs: TTypeWrapper): Boolean;
  24.     begin
  25.       result:=lhs<>rhs;
  26.     end;
  27.  
  28.     class operator TTypeWrapper.Explicit(const rec: TTypeWrapper): integer;
  29.     begin
  30.       result:=rec.data;
  31.     end;
  32.  
  33.     class operator TTypeWrapper.Explicit(const value: integer): TTypeWrapper;
  34.     begin
  35.       result.data:=value;
  36.     end;
  37.  
  38.  
  39.  
  40.  
  41.     type
  42.     t1 = record end;
  43.     t2 = record end;
  44.       THWND = specialize TTypeWrapper<t1>;
  45.       THandle = specialize TTypeWrapper<t2>;
  46.  
  47.  
  48.     var
  49.       hw: THWND;
  50.       hnd: THandle;
  51.     begin
  52.       hw := THWND(integer(0));
  53.       hnd := THandle(-1);
  54.       WriteLn(Integer(hw));
  55.       WriteLn(Integer(hnd));
  56.       hw := 0; // Error
  57.       hnd := hw; // Error
  58.     end.

You could put the specialize into a macro...

Warfley

  • Hero Member
  • *****
  • Posts: 2040
Re: Feature request: hard type creation/declaration
« Reply #58 on: July 08, 2025, 04:27:27 pm »
by doing an extra "type" THWND is a new type, not anymore the "specialized type". But the operator was specialized for the "specialized type". It does not work for the new distinct type created by the extra "type".

I mean it's very inconsistent as some operators do work (like the explicit operator into the other direction) while others don't work. Thats why I consider it a bug, either no operators should work or all of them. And as methods etc. all work I believe operators should as well

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12196
  • Debugger - SynEdit - and more
    • wiki
Re: Feature request: hard type creation/declaration
« Reply #59 on: July 08, 2025, 05:15:38 pm »
by doing an extra "type" THWND is a new type, not anymore the "specialized type". But the operator was specialized for the "specialized type". It does not work for the new distinct type created by the extra "type".

I mean it's very inconsistent as some operators do work (like the explicit operator into the other direction) while others don't work. Thats why I consider it a bug, either no operators should work or all of them. And as methods etc. all work I believe operators should as well

Yes, I realized that while you replied.

I myself more often define operators outside the type.

And then of course, I would not expect
Code: Pascal  [Select][+][-]
  1. operator := (a: integer): TFooRecord;
to work on
Code: Pascal  [Select][+][-]
  1. type TBar = type TFooRecord;

But, in your code operators were declared as part of the type.

So (probably) one could expect that, if a new type is created, those operators are copied/recreated in the same manner as fields are.



Btw, if you only need one base type, then you don't even need the generic.

 

TinyPortal © 2005-2018