Recent

Author Topic: FPC Unleashed (inline vars, statement expr, tuples, match, indexed/lazy labels)  (Read 41721 times)

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
Thanks for the clarification, @440bx - the single-arg restriction sidesteps the multi-arg objection. And on your point 2 - actually that already works for defer today. Defers fire at the matching end of whatever block they're declared in, not at the end of the routine. Quick demo:

Code: Pascal  [Select][+][-]
  1. program app;
  2. {$mode unleashed}
  3.  
  4. procedure main;
  5. begin
  6.   writeln('begin');
  7.   defer writeln('end');
  8.  
  9.   begin
  10.     defer writeln('end of block');
  11.     writeln('inside block');
  12.   end;
  13.  
  14.   if true then begin
  15.     defer writeln('exiting if');
  16.     writeln('im in the if');
  17.   end;
  18.  
  19.   writeln('bye');
  20. end;
  21.  
  22. begin
  23.   main;
  24.   readln;
  25. end.

Output:

Code: Text  [Select][+][-]
  1. begin
  2. inside block
  3. end of block
  4. im in the if
  5. exiting if
  6. bye
  7. end

So scope-of-declaration would carry over to declaration-attached defer for free.

The one thing I'm still stuck on is sentinel detection. The boolean defer-flag tracks "was the line reached", not "was the variable assigned" - and with declaration-attached defer, the flag fires on declaration, before init. To make it safe you'd need a sentinel value at declaration (= INVALID_HANDLE_VALUE, = nil, etc.) plus a runtime check before cleanup. Works for pointers and Windows handles, but plenty of types have no natural sentinel - booleans, integers where 0 is valid, plain records, custom enums.

And realistically - if you want RAII-style scoped cleanup for arbitrary resources, that's already covered by autofree (for class instances) and managed records (for value types with class operator Finalize). Wrap the THANDLE in a small class:

Code: Pascal  [Select][+][-]
  1. type
  2.   TScopedHandle = class
  3.     handle: THANDLE;
  4.     constructor Create(h: THANDLE=INVALID_HANDLE_VALUE);
  5.     destructor Destroy; override;
  6.   end;
  7.  
  8. constructor TScopedHandle.Create(h: THANDLE);
  9. begin
  10.   handle := h;
  11. end;
  12.  
  13. destructor TScopedHandle.Destroy;
  14. begin
  15.   if handle <> INVALID_HANDLE_VALUE then CloseHandle(handle);
  16. end;

Then in your code:

Code: Pascal  [Select][+][-]
  1. var token := autofree TScopedHandle.Create;
  2. // scope exit -> Free -> CloseHandle if valid

The cleanup is owned by the type, user just declares an instance, no separate defer needed. That's the RAII pattern, working today. The defer-on-variable proposal would be a third mechanism on top of two that already cover the same ground - which is part of why I'm hesitant.

Plus the reader-model thing - CloseHandle firing while the variable wasn't assigned (or was reset to sentinel) looks weird unless you know the mechanism.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6540
Just to clarify...

yes, the variable associated with a release function _must_ always be initialized to an invalid value (resource-wise) because the freeing function should only be executed with a valid value.

That makes the construct declarative, instead of being sequence dependent and prevents having the freeing function called wtih an invalid value.  Essentially, the initial value assigned to the variable serves as a flag and that is why it must be initialized for the declaration to work.

I'm glad to learn the deferred (defer) action is scoped since that's the way it should be.  I can see how what you're doing works but, I find that the declarative method is inherently clearer and less work for the programmer.   One statement says it all, it declares the variables, initializes it to an invalid value and tells the compiler what function to call to free the resource it represents.  No need to code a defer somewhere in the code, no need to explicitly test the handle's value in some destructor and, no need to mark it autofree since that's implicitly already done in the declaration.

The one downside of the declarative method is that it can only be used with procedures or functions that take only 1 parameter to release the resource (the variable itself), that's the limitation paid for its succinctness.

By the way... it's just a suggestion.  I made it because you showed interest in RAII and that is a form of it.


FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

VisualLab

  • Hero Member
  • *****
  • Posts: 742

Now, about the keyword itself - static, persistent, or keep?

I'd go with either static or keep. Both are short, easy to type, and immediately clear in meaning. persistent is descriptive but annoying to type (and easy to mistype) - not a great quality for a keyword you'll use regularly. Between the two, static has prior art in C/C++ and maps to the exact same concept. keep is a fresh alternative if we want to avoid the C baggage. Either way, both are better than persistent purely from a practical standpoint.

While I agree from an English language perspective, in data handling circles persistence refers to data storage outside of the process. Typically storage to file or database (e.g. tiOPF).

I've read everyone's view on what the name of the feature should be and, I still prefer persistent, second choice would be static because it is already known (maybe) to programmers familiar with C and C++, which I believe is a large percentage of programmers but, the word "static", unlike "persistent" does not really indicate that the variable's existence is independent of the scope in which it is declared.

persistent is the semantically logical choice.

Another possibility that comes to mind is owned denoting that it resides in global storage but it is owned by the function/procedure in which it is declared.  I still prefer persistent.

Let me remind you that in the FCL library there is a class called TPersistent. Although it has a name preceded by the letter "T", using a keyword similar to an already used class name can be misleading. Moreover, in many Object Pascal libraries the name Owner is used: for arguments of constructors or subroutines, for properties and fields (they have a prefix in the form of the letter "F"). This can also be confusing.



So as an alternative to a "var" block with the "static" attribute, we could introduce the globalvar block. It would work like a global var block, except that the variables declared in it are only known to the subroutine.

Won't using such a keyword (globalvar) cause problems for Pascal or more opponents? Because there is a similar keyword (and for a similar use) in PHP, which, as we know, is not considered a good language design.



Pascal emphasizes keeping like things together, that is, a section for types, a section for constants, a section for local variables and IMO, persistent variables are different enough to merit their own section.  The thought of having to read the entire variable section to determine if there are persistent variables is time consuming and error prone, therefore unappealing.

I also think this is a desirable approach.



I suggest you use const, so it would differentiate from C, and we are all used to it. So it would mean 'variable' which is 'persistent' (constant). It would look like this:
Code: Pascal  [Select][+][-]
  1. var x: Integer = 1; const;
  2.  

Is this a joke? This is internally contradictory. This is a very bad idea.



Condition-based: match without "of"

No subject - every branch is just a boolean. A clean replacement for if/else if ladders:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_condition;
  4.  
  5. var
  6.   x: Integer;
  7. begin
  8.   x := 50;
  9.   match
  10.     x > 100: WriteLn('huge');
  11.     x > 10:  WriteLn('medium');
  12.     x > 0:   WriteLn('small');
  13.     _:       WriteLn('non-positive');
  14.   end;
  15.  
  16.   ReadLn;
  17. end.

Output:

Code: Text  [Select][+][-]
  1. medium

A very useful idea, but the need to use the "_" character spoils the whole thing (too python-like). I strongly agree with:

ETA:

One concern though, the underscore is a valid character in an identifier, the "_:" should cause the _scanner_ to consider it (identify it as) an identifier when in that context, it is not.  For instance, presuming there is an identifier named "_" and the conditions are "_ > 100", "_ > 10", "_ > 0" and "_ { spaces or comment } :", the scanner can't decide what the underscore means.



~? Bro, have you ever actually typed a tilde? I mean yeah, you clearly put one in your post, but maybe you copy-pasted it from somewhere :D It's shift+`, then SPACE to commit it, otherwise it just sits there waiting to eat your next letter. Way too much work for "skip this field". _ is shift+minus, one motion, done :D

That came to my mind immediately too. :)



Tuples, patterns, underscores,  match  expressions... is FPC Unleashed turning into Rust?  :D

Not yet. Only when "borrowing" is implemented will this happen :D



NOTE: a possible alternative to "distinct" would be "strict".

But "strict" is already used in visibility sections in class definitions.

440bx

  • Hero Member
  • *****
  • Posts: 6540
@VisualLab,

Your concerns about some of the words, or similar words, I proposed already being used in other places such as the FCL and OOP constructs is valid.  The problem is, I never think about those areas because I don't ever use any of it.  Anyway, that's just FYI, because, as mentioned, I think your concerns are valid but, I wouldn't have thought about them because, for practical purposes, those ares of the language don't exist in my mind.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12905
  • FPC developer.
But "strict" is already used in visibility sections in class definitions.

Well, but since this is the "unleashed" thread, any baroque syntax goes! Why not have "strict strict"  ?

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
Mini announce: with var NAME: TYPE do (Form D)

A few posts back @Okoba asked whether with var T: TTest1 do - inline-var with an explicit type and no initializer - was possible. At the time it wasn't. It is now: Form D of scoped with.

Syntax:

Code: Pascal  [Select][+][-]
  1. with var NAME: TYPE [:= [autofree] INIT] do BODY

Mirror of the existing Form C (inline-var with type inference) but with an explicit type instead. The initializer is optional, and accepts the same autofree modifier when present.

Without initializer - useful for stack-allocating a record local that the body fills in directly:

Code: Pascal  [Select][+][-]
  1. type TPoint = record a: integer; b: dword; end;
  2.  
  3. with var t: TPoint do begin
  4.   t.a := 10; // or without "t."
  5.   t.b := 20; // or without "t."
  6.   Process(t);
  7. end;
  8. // t goes out of scope here

The variable is left at its default state (managed types init'd, ordinals zero, records uninitialized) with varstate vs_declared, so a read-before-write triggers the regular uninitialized-variable warning.

With initializer - any expression assignable to the declared type, including aggregate literals (the same (field: val; ...) / (a, b, c) shape accepted by typed constants):

Code: Pascal  [Select][+][-]
  1. // record aggregate
  2. with var p: TPoint := (a: 1; b: 2) do
  3.   writeln(a, ' ', b);              // 1 2
  4.  
  5. // regular expression init (record copy)
  6. var src: TPoint;
  7. src.a := 100; src.b := 200;
  8. with var p: TPoint := src do
  9.   writeln(p.a, ' ', p.b);          // 100 200
  10.  
  11. // anonymous record type
  12. with var q: record x, y: integer; end := (x: 5; y: 6) do
  13.   writeln(x, ' ', y);              // 5 6

With autofree - explicit-type variant of Form C's auto-cleanup, useful when you want the type written in source rather than inferred:

Code: Pascal  [Select][+][-]
  1. with var a: TStringList := autofree TStringList.Create do
  2.   a.Add('hello');
  3. // a.Free called here

autofree requires a class derived from TObject; aggregate-literal init on records/arrays still rejects it. Without an initializer there is no instance to free, so autofree only makes sense in the := EXPR form.



Bonus - bug fix while I was at it

While testing Form D I caught a bug in the existing classical autofree form (predates Form D, has been there a while):

Code: Pascal  [Select][+][-]
  1. program test3_classical_in_try;
  2.  
  3. {$mode unleashed}
  4.  
  5. uses Classes;
  6.  
  7. var
  8.   list: TStringList;
  9.  
  10. begin
  11.   try
  12.     list := autofree TStringList.Create;
  13.   except
  14.     writeln('exception during create');
  15.     readln;
  16.     halt(1);
  17.   end;
  18.  
  19.   writeln(if list = nil then 'BUG!' else 'OK!'); // prints "BUG!"
  20.  
  21.   readln;
  22. end.

The variable came back as nil after the try block, even though autofree TStringList.Create should have left it pointing at a valid instance. Just fixed - all autofree / scoped with paths should behave correctly now.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
Well, but since this is the "unleashed" thread, any baroque syntax goes! Why not have "strict strict"  ?

Better tell me when my upstream MRs are going to get merged :) 10 of them open, mostly compiler bugfixes. Until they start moving, opening new ones feels pointless - might as well put the time into Unleashed.

Why even bother with upstream MRs? Faster to land the fix in Unleashed directly than to wait years for the upstream merge so I can sync it back into the fork.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6540
if you are going to allow this:
Code: Pascal  [Select][+][-]
  1. type TPoint = record a: integer; b: dword; end;
  2.  
  3. with var t: TPoint do begin
  4.   t.a := 10; // or without "t."
  5.   t.b := 20; // or without "t."
  6.   Process(t);
  7. end;
  8. // t goes out of scope here
then consistency would require that you also allow defining an inline variable for a "while" loop.  Something along the lines of:
Code: Pascal  [Select][+][-]
  1. while var i : integer := <somevalue> do
  2. begin
  3. end;
  4.  
which, like when it's used in a "for" loop and, now the enhanced "with" version, clearly denotes that the variable only exists in the scope of the "while".  That's the one missing instance I can think of at this time where an inline declaration would add consistency to the language.  The key part of the previous sentence is "I can think of at this time", this is my subtle way of recommending you double check there aren't other instances where allowing the inline var declaration would add to language consistency.

HTH.

« Last Edit: May 10, 2026, 12:10:22 am by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
then consistency would require that you also allow defining an inline variable for a "while" loop.  Something along the lines of:
Code: Pascal  [Select][+][-]
  1. while var i : integer := <somevalue> do
  2. begin
  3. end;
  4.  

Doesn't fit. while needs a boolean condition each iteration; var i: integer := X is a declaration that yields an integer, not a condition. for and with worked because they have natural iteration / scope-opening semantics; while doesn't. The same scoping is already available via begin var i := X; while i <> 0 do begin ... end end - clear condition, no new syntax needed.

Maybe something like this could work, off the top of my head:

Code: Pascal  [Select][+][-]
  1. while var line := file_get_line(); line <> '' do ...

Declaration + separate condition, separated by ;. But that's a brand new syntax shape just to save a few lines - not sure it pulls its weight.

Honestly though, I'd leave it as-is. The inline-var inside the surrounding begin..end already does the job - problem solved. No need to compress it all the way into the while header itself.

Just thinking out loud here - no commitments, just throwing ideas around.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6540
Doesn't fit.
You are absolutely right.  There is obviously still a lot of C influence lingering in my mind and, on that note and, strictly thinking out loud, maybe allowing the following would be ok:
Code: Pascal  [Select][+][-]
  1. while BOOL(var i : integer := <somevalue>) do
  2. begin
  3. end;
  4.  
the typecast takes care of the flaw you pointed out in my original example but, I agree with you, I have mixed feelings about whether or not that addition is worth it.

The one thing I like about it is that it would make some ported C code be 1:1 with Pascal code.  It's nice when that is possible, less mistakes that way.





FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 19273
  • Glad to be alive.
As long this keeps working.....
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. // full program
  3. function bool(const a:integer = 0):integer;
  4. begin
  5.   Result := a;
  6. end;
  7.  
  8. begin
  9.   while bool = 0 do; // press ctrl-c to end
  10. end.
Because that is already legal syntax.
« Last Edit: May 10, 2026, 01:58:19 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Fibonacci

  • Hero Member
  • *****
  • Posts: 1002
  • Behold, I bring salvation - FPC Unleashed
As long this keeps working.....
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}
  2. // full program
  3. function bool(const a:integer = 0):integer;
  4. begin
  5.   Result := a;
  6. end;
  7.  
  8. begin
  9.   while bool = 0 do; // press ctrl-c to end
  10. end.
Because that is already legal syntax.

Relax - if such a syntax ever landed, it would be a contextual keyword, so your function stays legal. But it probably won't land at all - it was just a loose idea @440bx threw out, brainstorm.

By contextual keyword I mean for example all in match all - all is free, it's not a keyword, but the IDE treats it specially and highlights it like a keyword when it sits in that specific context. Same with step in for - you can have a variable named step and use the contextual step at the same time; only the second one gets highlighted (if you use the Unleashed IDE).
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

VisualLab

  • Hero Member
  • *****
  • Posts: 742
There is obviously still a lot of C influence lingering in my mind and, on that note and, strictly thinking out loud, maybe allowing the following would be ok:
Code: Pascal  [Select][+][-]
  1. while BOOL(var i : integer := <somevalue>) do
  2. begin
  3. end;
  4.  
the typecast takes care of the flaw you pointed out in my original example but, I agree with you, I have mixed feelings about whether or not that addition is worth it.

The one thing I like about it is that it would make some ported C code be 1:1 with Pascal code.  It's nice when that is possible, less mistakes that way.

Fibonacci, 440bx, if you both overdo it with the number of language modifications, it'll end up being a monstrosity even more unreadable than the latest versions of C++ or Rust. I understand the intentions (some of them quite right), but there is a saying that "the road to hell is paved with good intentions" for a reason. The intention may be important, but it would be good if its implementation was no worse than the plague.

440bx

  • Hero Member
  • *****
  • Posts: 6540
Fibonacci, 440bx, if you both overdo it with the number of language modifications, it'll end up being a monstrosity even more unreadable than the latest versions of C++ or Rust.
Believe it or not, I agree with you but, I think the "too many features" ship sailed, long, long ago.

At this point, I see adding features that solve specific and commonly found problems as appropriate, e.g, FAMs, I also like the idea of being able to write:
Code: Pascal  [Select][+][-]
  1. while BOOL(var i : integer := <somevalue>) do...
for two reasons, the first is that it declares that the value of "i" cannot be used outside the while loop and, that is good for the programmer to know upfront, the second reason I like it is because it really simplifies porting C code.  That construction is extremely common in C code and, whether we like it or not (I certainly don't like it) we live in a C centric world (at least for the foreseeable future.)  Lastly, as a bonus, it removes one variable from the declarations section and, one problem in Pascal is the tendency to have what are essentially short-lived, disposable variables live in the full scope of the function or procedure, they shouldn't, that's a deficiency in the language.  The real solution to that problem is for the language to have the ability to define local scopes, such as Ada's "declare", that goes a long ways to making functions and procedures clean but, that seems to be too much of a departure for Pascal programmers, instead we have C's absurdly poor design: inline variables, which pretty amounts to data spaghetti.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

LeP

  • Sr. Member
  • ****
  • Posts: 347
I think deviating too far and too quickly from the Pascal philosophy is a mistake.
Updates and additions to the language are certainly being made, but they should be aimed at improving the programming logic, not copying existing languages.
Why not take something from "React" or "Svelte 3"?
Are we in a "C"-centric world? Very well, now is the time to get rid of C, not to appropriate its semantics.

The inability of programmers to develop (which often amounts to just copying) in one language rather than another is embarrassing: if you don't know how or don't want to... you simply work with others who do.

Rather than demanding syntax and compiler capabilities that devalue Pascal, it would be better to do more by enhancing and fixing what's available now.

For example, in Delphi, "for in" has been available for a long time in many components; in FPC/Lazarus, it still seems (my fault if it's not true) that enumerators are unknown.

Instead of implementing new "FOR" statements, let's enhance what already exists.

Sorry if I was a bit rude in this post, but there are a number of reasons, such as backward/future compatibility of the code, readability, environment maintenance, and the waste of effort (especially future) maintaining the various development branches.
I love Pascal, and although I use Delphi, I think FPC/Lazarus should continue to be a valid alternative.
Un Sistema per domarli, un IDE per trovarli, un codice per ghermirli e nel framework incatenarli.
An operating system to tame them, an IDE to find them, a code to catch them and in the framework chain them.

 

TinyPortal © 2005-2018