Recent

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

Fibonacci

  • Hero Member
  • *****
  • Posts: 996
  • Behold, I bring salvation - FPC Unleashed
Let's say that's exactly why "_" exists, and should be used instead of else / otherwise :D
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Thausand

  • Hero Member
  • *****
  • Posts: 560
@Fibonacci:
Have I correct understand for statement match is work same/similar as is implement when programming language rust ? And then use "_" same ?
A docile goblin always follow HERMES.md

Fibonacci

  • Hero Member
  • *****
  • Posts: 996
  • Behold, I bring salvation - FPC Unleashed
I don't really code in Rust, so I can't say it's identical. But the concept is common to a lot of languages: you match a value against patterns/ranges, and _ is the wildcard - it matches anything not covered above.

Plain match runs only the first matching branch. match all runs every matching branch (fall-through).
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Thaddy

  • Hero Member
  • *****
  • Posts: 19249
  • Glad to be alive.
It - underscore - is indeed better than else or otherwise etc, because that causes confusion when fall through.
Would simplify the syntax, but beware of my first concern: since _ is part of allowed names ot also can lead to confusion. See my example of the ambiguous _ with a class with the same name in scope.

Note, apart from inline vars/labels, I am starting to like it.
« Last Edit: June 02, 2026, 02:50:34 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

440bx

  • Hero Member
  • *****
  • Posts: 6528
The underscore in that location is a poor choice because a single underscore is also a valid identifier, therefore _: is inherently ambiguous, is it an identifier followed by a colon or a wildcard ?  the underscore's natural meaning which is that of being an identifier is being overridden by the statement in which it appears.

In Pascal, a better choice would have been * which is also often used to mean any and, unlike the _, the combination *: does not already have a specific meaning in Pascal, therefore just by looking at the next token which is ":" the parser can assign the meaning of "any" to the underscore in that location.  Simpler, intuitive and doesn't create a weird situation.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

VisualLab

  • Hero Member
  • *****
  • Posts: 742
Because their creators were guided by the syntax of C or its derivatives (C++, Java, etc.).

So what? New programming languages weren’t designed to be syntactically compatible with C, but rather so that their syntax would be sufficiently good, readable, and understandable. And in the case of arrays, a syntax similar to that of C was used, not because of thoughtless copying of ideas from C, but because it is simply good—for most programmers, though apparently not for Pascal programmers.

And how can one recognize sufficiently good, readable, and understandable syntax? This is a rather subjective matter. What is good/correct/readable/understandable for one person will be either verbose, overly condensed, or even unreadable for another. Slightly longer syntax is usually more readable, although some people may perceive it as verbose. It's always a compromise.

However, you should not overdo it with shortening the notation. A good example of poor syntax design is C and its descendant, C++. In the pursuit of "typing code faster," the syntax of these languages has been oversimplified to the point of absurdity. The real reason for this simplification, however, is not the mythical desire to "enter code faster", but rather the hardware limitations of the computers (they were quite primitive) on which their creators worked (i.e. B, BCPL, C). However, not everyone assumed that progress in computer development would be so slow that every typed character had to be conserved. This was simple "computer avarice" combined with a naive belief in the slow development of computers, not any foresight on the part of the designers of B, BCPL, and C. However, some people love to believe in nonsense myths.

Quote
Furthermore, the range-based syntax still allows you to easily determine the number of array elements.

So? Has anyone suggested removing the ability to specify indices? No, no one has suggested that. The proposal is to extend the syntax for declaring arrays so that you can specify the number of elements and use default indexing starting from 0.

And who will guarantee that this syntax will not be abused?

However, the above proposal, with empty (or partially empty) square brackets, can be confused with code in which someone forgot to specify a size or index. The compiler won't be able to distinguish this, and consequently won't be able to report incomplete (and therefore incorrect) syntax. After compilation, this may result in incorrect program operation and the need for time-consuming error searching.

Nonsense. If a programmer forgets to specify the size of an array or its indices, that array will be empty (automatically initialized to nil), and the first attempt to access it will cause a runtime error, so there’s nothing to look for. On the other hand, if a programmer has memory issues, no syntax—not even the most verbose one—will help. Arguments like "no, because someone might forget" aren’t valid arguments.

But it is a completely human trait to forget something, to make a mistake. For various reasons. Most often, it's probably due to fatigue. You don't consider that syntax can be more or less error-prone. Furthermore, incomplete syntax is incorrect, because what else is the compiler supposed to do—add the missing fragment itself? Moreover, the most desirable situation is when it is possible to detect an error in the code at the compilation stage (which, as we know, is not always possible). A runtime error is more severe, and it's not so bad when it's repeatable. It's worse when it only occurs in certain specific situations. However, there is not always a guarantee that an error made in the code will be repeated. So, a slightly more verbose syntax is more profitable, because it saves time that would otherwise be spent debugging and fixing some errors. And the larger the program, the greater the likelihood of errors. Therefore, it's good to reduce the likelihood of making at least some of them.

Quote
The current declaration of dynamic arrays in Pascal is much more readable and incomparably less error-prone.

"It is not known how many users there are of particular syntaxes and what cases they need. There is no information about anyone conducting any research or surveys on this subject." So in short — "No, it's just your subjective opinion that the current Pascal array declaration syntax is not too long. Not everyone agrees."

This may be a subjective opinion, but it's certainly not just mine. The comments on this forum prove that there are many people who prefer the more readable syntax. I'd venture to say that most people probably prefer the more readable Pascal syntax.

Quote
Another error-prone suggestion. It's illegible.

"Just your subjective opinion, not everyone agrees."

Well, the comments show otherwise. Despite the subjectivity of "my opinion" :)

Quote
After all, C already exists, has a lot of libraries and quite efficient compilers. So just use it instead of trying to force Pascal to resemble C.

After all, original Free Pascal already and still exists, has verbose syntax and is less C-like. So use it instead of criticizing the development of a language fork that had features from C and other languages implemented long before I stumbled upon this thread and this project.

And after many years, some of them believe this wasn't a good idea (I'm referring to the discussions over the last few years). Support for certain C constructs (e.g., unions or bit fields) is desirable in Pascal for systemic reasons (e.g., their absence in Pascal), not because it's a good idea. However, the alternative C-style syntax to the existing Pascal syntax is: (a) unnecessary duplication and less readable, (b) more error-prone, and (c) unnecessary complexity for the compiler code.

440bx

  • Hero Member
  • *****
  • Posts: 6528
Feature request:

There are a good number of times when it is necessary to refer to the type of an existing variable and it would be rather nice to be able to do that.  For example:
Code: Pascal  [Select][+][-]
  1. function Min(A: array of Integer): Integer;
  2. begin
  3.   Result := High(type(A));
  4.  
  5.   for each A do
  6.     if A < Result then
  7.       Result := A;
  8. end;
  9.  
Result is initialized using the type of the array A instead of using the fragile High(integer) which needs to be changed if the array type changes.  This type decoupling causes what's known as type drift and can cause subtle and difficult bugs to find when a type is changed to another type that is compatible with the original type.

In the above example, the keyword "type" has been overloaded to return the terminal value type of its parameter which should be a variable.  Its primary purpose is to allow code to refer to the type of a variable without explicitly repeating the type.  An important characteristic is that "type()" is a compile time function, it is not part of RTTI or any other run time associated type mechanism.

One important characteristic is that "type()" cannot and does not return the type of an anonymous record since it doesn't make sense to dig into the record to determine a type (unlike in the case of an array.)

The result of type() is basically a type entry in the compiler's symbol table which can be referenced by another identifier that should be of the same type.  Simple stuff.


« Last Edit: June 04, 2026, 02:23:44 pm by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12398
  • Debugger - SynEdit - and more
    • wiki
There are a good number of times when it is necessary to refer to the type of an existing variable and it would be rather nice to be able to do that.  For example:
Code: Pascal  [Select][+][-]
  1. function Min(A: array of Integer): Integer;
  2. begin
  3.   Result := High(type(A));
  4.  
  5.   for each A do
  6.     if A < Result then
  7.       Result := A;
  8. end;
  9.  

Doesn't that already exist with generics?

From memory:
Code: Pascal  [Select][+][-]
  1.       {$ModeSwitch implicitfunctionspecialization}
  2.       generic function HighOfType<T>(a: T): Int64; inline;
  3.       begin
  4.          result := high(T);
  5.       end;

EDIT:

Your example does not refer to the element? Maybe you meant "high(type(a[0]))" ? As the type of just A is the array, not integer.

Anyway, the generic can be changed to do either.
Code: Pascal  [Select][+][-]
  1.          result := high(T[low(T)]); // use first index / add {R-} if needed
« Last Edit: June 04, 2026, 03:10:32 pm by Martin_fr »

440bx

  • Hero Member
  • *****
  • Posts: 6528
The example is purposely simple just to illustrate the concept.  It could, in that case, be done using "high(type(a[0]))".

Generics are not a solution because they apply to generics only and, the purpose of that feature is to be applicable to any code.  Not to mention that they usually require a lot of syntactic structure to define them and specialize them.  Not worth all  the hassle just to access the type of a variable (which may not even be part of the generic anyway.)

There are times when a variable somewhere needs to be the same type as a previously defined variable and the type could be any ordinal type, a float type or a record type, currently the language does not offer a way of declaring "var A : ThetypeofpreviouslydefinedvariableB;", that's the unsolved problem and that is what causes type drift and unexpected bugs when one type is modified someplace and another type, that is supposed to be the same somewhere else, isn't.

The problem is that examples of those situations tend to be more complicated.  That's why I used an example that is simple but, in that one in particular, there is a solution to accomplish what it does.  That said, if you wanted, for any reason, to declare a variable that is of the same type as the array element type, maybe because you intend to swap array elements or some other thing like that where you need a temporary, there is no way to do it short of modifying the entire thing to use generics which is way overkill just to match one type to another.

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

Thaddy

  • Hero Member
  • *****
  • Posts: 19249
  • Glad to be alive.
Are extensions to type restrictors already implemented: generic procedure<T:ordinal> is the most important one. Where ordinal can be checked against a subset of TTypeKind and include or exclude int64 or qword based on 32/64 bit (tkInteger, tkChar, tkBool, tkInt64,tkQword)
likewise in for enumeration types in system.
Code: Pascal  [Select][+][-]
  1. program app;
  2. {$mode unleashed}
  3. {$modeswitch implicitfunctionspecialization}
  4. (*
  5. Ideal way:
  6.  
  7. procedure test<T:ordinal>(value:T);
  8. begin
  9.   writeln('I know T is an ordinal');
  10. end;
  11.  
  12. Cumbersome current way follows:
  13. *)
  14.  
  15. const
  16.   Ordinals:set of TTypeKind = [tkInteger, tkChar, tkBool, tkInt64, tkQword];// in order
  17.  
  18.   procedure test<T>(value:T);
  19.   begin
  20.     if gettypekind(T) in Ordinals then
  21.       writeln('is ordinal') // perform action
  22.     else
  23.       writeln('not ordinal'); // ignore
  24.   end;
  25.  
  26. var
  27.   s:string;
  28.   i:integer;
  29. begin
  30.   test(s);
  31.   test(i);
  32.   readln;
  33. end.
   
« Last Edit: June 04, 2026, 06:18:01 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

bytebites

  • Hero Member
  • *****
  • Posts: 787
The example is purposely simple just to illustrate the concept.  It could, in that case, be done using "high(type(a[0]))".

Generics are not a solution because they apply to generics only and, the purpose of that feature is to be applicable to any code.  Not to mention that they usually require a lot of syntactic structure to define them and specialize them.  Not worth all  the hassle just to access the type of a variable (which may not even be part of the generic anyway.)

There are times when a variable somewhere needs to be the same type as a previously defined variable and the type could be any ordinal type, a float type or a record type, currently the language does not offer a way of declaring "var A : ThetypeofpreviouslydefinedvariableB;", that's the unsolved problem and that is what causes type drift and unexpected bugs when one type is modified someplace and another type, that is supposed to be the same somewhere else, isn't.

The problem is that examples of those situations tend to be more complicated.  That's why I used an example that is simple but, in that one in particular, there is a solution to accomplish what it does.  That said, if you wanted, for any reason, to declare a variable that is of the same type as the array element type, maybe because you intend to swap array elements or some other thing like that where you need a temporary, there is no way to do it short of modifying the entire thing to use generics which is way overkill just to match one type to another.

Why  High(A[0])  isn't sufficient?

440bx

  • Hero Member
  • *****
  • Posts: 6528
Why  High(A[0])  isn't sufficient?
It is sufficient in the particularly simple example I provided but, even in that one, if you needed a temporary variable of the same type as the array elements, there is no simple way to declare such a temporary.  That's what type() would provide.  For the record, having to declare a temporary variable that needs to be of the same type of another variable is a common occurrence, not rare at all.

@Thaddy,

While generics could be used, it's like using a tank to swat a fly.  It's way too heavy and complicated for something so simple.  In addition to that, that really isn't what generics are for.  Generics are supposed to be used to instantiate versions of the code for different types, not just to get two variables to be of the same type.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

flowCRANE

  • Hero Member
  • *****
  • Posts: 986
@Fibonacci: what about static variables, i.e., the ones that are declared inside the function but preserves their values between separate function calls? Are they already supported, and if not, have you considered adding them to the language? Free Pascal doesn't offer this at all, and such variables do have their uses.

I know we can simulate them by declaring a typed constant inside a function, but this approach depends on the {$WRITEABLECONST} directive, so modifying such constants may not be possible.
Lazarus 4.6 with FPC 3.2.2, Windows 11 — all 64-bit

Working solo on a top-down retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

jamie

  • Hero Member
  • *****
  • Posts: 7761
@Fibonacci
  I just breezed through this posts and I must say I like a lot the features you have put in there, if they all work as planned?

 For example,

Code: Pascal  [Select][+][-]
  1. AVar := If <Bool test> Then Results1 else Results 2;
  2.  

  The main advantage there is that you don't resolve any of the result1, or 2 until the Boolean is found and at that point then resolve only the correct one. This is because instead of having a constant value to return, it could be calling a function for the result, and you only want to call the correct function and not both before performing the evaluation.

  Also, they have to be able to nest within the results in case another IF ... THEN is performed.

---
 As for Arrays having a Size instead of a RANGE to define them I find that also great for two reasons, its shorter and they would naturally default to starting at 0 index, also you can specify 0 for a size.
Code: Pascal  [Select][+][-]
  1. MyArray :Array[0] of WhatEvet;
  2.  

 You say what would that be good for? Lots of things, you can make UNIONS like that with various types, just define them in records one after another together all being 0 size.  I know that can be done with variant records but only at the end.

 Also, there are many cases where you place a size less array at the end of a record so you can index beyond the record which is common in many cases. Currently we can do that with a RECCORD END; but it needs type casting or special Generics Type for clean reading.

--

 The inline variables, well, I am a little against that however, if the Scope is to be within the current block then that is just wonderful, I can picture lots of code using that to keep the variables in front of you and also only scoped to the current block, there by not allowing code outside accessing it.

  For the most part, I loke most of what you doing.

Jamie







The only true wisdom is knowing you know nothing

Thausand

  • Hero Member
  • *****
  • Posts: 560
You say what would that be good for? Lots of things, you can make UNIONS like that with various types, just define them in records one after another together all being 0 size.  I know that can be done with variant records but only at the end.
also have unleased feature composable records (have union) that can do same: https://forum.lazarus.freepascal.org/index.php/topic,73678.msg583194.html#msg583194
A docile goblin always follow HERMES.md

 

TinyPortal © 2005-2018