Recent

Author Topic: I hope FreePascal can support syntax like Python's f'string {variable}' or ...  (Read 4085 times)

xiyi0616

  • Jr. Member
  • **
  • Posts: 64
Quote
Reading the following piece of code i know/can see exactly what's happening
Code: Pascal  [Select][+][-]
  1. Var  
  2.    s,t, Name:String;
  3.    Age:Integer;
  4. //....
  5. Begin
  6.   Name:='Zvoni';
  7.   Age:=12; //Yeah...Laugh it up :) Not telling you my Age
  8.   s:='Hi. My Name is '+Name+' and i am '+Age.ToString+' years old';
  9.   t:=Format('Hi. My Name is %s and i am %d years old',[name,age]);
  10. End;
I personally think the addition of this $support is excellent. Let me challenge Zvoni’s point from my own perspective, using the example you gave. Take the piece of code like ...+ Age.ToString + ...— usually you don’t actually have a second choice here. Could you write ...+ Age.ToInteger + ...or ...+ Age.ToFloat + ...instead? Unless you write your own function (though that’s a different scene), not to mention using format. We know what we want: a string output of certain information. Unless necessary, why should we have to remember those %s, %d, or %fspecifiers? Before, we simply had no better alternatives and had to do it that way. Of course, there are still cases where +and formatare useful, but in most scenarios now, the $syntax is more appropriate and readable.

Finally, The key point is that this is not contradictory at all!
s := $'My name is {Name}. My age is ' + Format('%.2f', [someF]);

Supporting this format does not mean abandoning precise control over the code. Rather, it enables writing code efficiently with high readability, while maintaining the option for precise control when needed.

Improvement often comes from having more choices, and I hope FreePascal will continue to support more new features like this.
« Last Edit: April 18, 2026, 02:41:41 am by xiyi0616 »

LeP

  • Sr. Member
  • ****
  • Posts: 294
@xiyi0616
In Pascal there is a type that can contain all "things", and is calling variant type.

In this case you don't have to put ".toString" (or choose when to put) at design time, you can write:

Code: Pascal  [Select][+][-]
  1.  var Name, Age: variant;
  2. Name := 'Maria';
  3. Age := 6; //or Age := 'six';
  4. 'My name is '+String(Name)+' my age is '+String(Age);

Doesn't mind what is the contenet of variant, the RTL try to convert  (but at runtime, not at compiler time). Is similar in what is done in Python at runtime.
But you will not see too much use of variant in Pascal (Lazrus / FPC / Delphi) 'cause who use Pascal think about of a "solid" type descriptor.
« Last Edit: April 16, 2026, 05:09:40 pm by LeP »
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.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12345
  • Debugger - SynEdit - and more
    • wiki
The entire "foo.ToString" is IMHO showing a lack of understanding.

First of all, each and every language has its own concepts. The same thing will be done different in different languages. That is not only ok, that is really really good.

In Pascal the syntax is:
Code: Pascal  [Select][+][-]
  1. var s: string; i: integer;
  2. begin
  3.   WriteStr(s, 'number:',i);

There is no "tostring".
There is syntactically a function call. But in the resulting exe that is left to the compiler. The same as any compiler could or could not embed function calls for interpolated strings.

Don't mix up the generated asm with the high level syntax. The syntax does not dictate the asm. If you don't wont function calls in the asm, then get the existing syntax optimized for that.

For all, else what is the difference to
Code: Text  [Select][+][-]
  1. s := $'Number {i}'

":= $" is a replacement for "writestr" => its a minimal bit shorter, but way less expressive. Imho that is like "can we have { for begin"?

"{" and "}" are replacements for ', and ,'




So I really can't help but get the idea that you are "just used to some other language" and you just want Pascal to be more similar, so you don't need to think in Pascal-terms.

But then, why bother with Pascal at all? Seriously?

Do we really need an additional string terminator? Just to have one that "includes" the ","?


xiyi0616

  • Jr. Member
  • **
  • Posts: 64
thanks, @LeP.
I've heard that variants are slow to process — not that I can't handle them.  :)

Quote
But then, why bother with Pascal at all? Seriously?

Do we really need an additional string terminator? Just to have one that "includes" the ","?
I'm not trying to change how everyone writes Pascal. Python has its strengths, but Lazarus doesn't need to copy everything. My point is specifically about this issue: Formatisn't very intuitive, it's inconvenient to use, and the readability is poor. We humans can't just connect to the internet for extra brainpower, so anything unnecessary should be avoided to save mental energy.

Take time handling as an example: different systems store and interpret time differently, but we shouldn't have to learn each system's conversion method. I think Lazarus should provide unified functions like TimeToStr. When using it, we should just be able to write:

Edit1.Text := 'Now is ' + TimeToStr(Now());

But if we're going to standardize on just this function, why do we even need to remember its name, let alone which unit it belongs to? Why not simply write:

Edit1.Text := $'Now is {Now()}'(assuming Now() works here :))

This $syntax wouldn't conflict with existing styles—it just adds options. You could still write FreePascal the way you prefer.
« Last Edit: April 18, 2026, 07:35:09 am by xiyi0616 »

Fibonacci

  • Hero Member
  • *****
  • Posts: 941
  • Behold, I bring salvation - FPC Unleashed
But if we're going to standardize on just this function, why do we even need to remember its name, let alone which unit it belongs to? Why not simply write:

Edit1.Text := $'Now is {Now()}'(assuming Now()works here :))

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. uses SysUtils {Now, FormatDateTime}, DateUtils {EndOfTheDay};
  4.  
  5. procedure main;
  6. begin
  7.   writeln($'Now is: {Now}');
  8.   writeln($'Now is: {TimeToStr(Now)}');
  9.   writeln($'Time to midnight: {FormatDateTime('hh:nn:ss', EndOfTheDay(Now)-Now)}');
  10.   readln;
  11. end;
  12.  
  13. begin
  14.   main;
  15. end.

Code: Text  [Select][+][-]
  1. Now is: 46129.13375964120500000000
  2. Now is: 03:12:36
  3. Time to midnight: 20:47:23

* only on feat-strinterp branch, may or may not land in main
« Last Edit: April 17, 2026, 04:08:53 am by Fibonacci »
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: 941
  • Behold, I bring salvation - FPC Unleashed
":= $" is a replacement for "writestr" => its a minimal bit shorter, but way less expressive.

Yes, interpolated strings internally use WriteStr. But they can be extended with new formatting options, for example with TDateTime some custom formatting could be applied, maybe even something like:

Code: Pascal  [Select][+][-]
  1. $'Now is {Now as 'hh:nn:ss'}'

its a minimal bit shorter, but way less expressive. Imho that is like "can we have { for begin"?

True for a single variable, but imagine you have 5 of them. With WriteStr it quickly turns into a long multi-line statement, while interpolation keeps it on one line and readable.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

xiyi0616

  • Jr. Member
  • **
  • Posts: 64
As a Lazarus beginner, thanks to Fibonacci for the work.
« Last Edit: April 17, 2026, 06:30:46 am by xiyi0616 »

kupferstecher

  • Hero Member
  • *****
  • Posts: 618
Code: Pascal  [Select][+][-]
  1.   writeln($'Now is: {Now}');
The curly brackets I think are not a good choice due to the comments (and also they are ugly). I'd prefer square brackets.
About the dollar sign I'm not so sure, on the one hand I don't really like it, but it is much better than the ´-character that was chosen in trunk for the multi line strings, because the string signs ' ' are kept which reduces confusion. Also I would suggest to combine the string insertion feature with multiline strings, so that there are not three different ways of strings.

Code: Pascal  [Select][+][-]
  1.   writeln($'Thanks for
  2. reading, [CurrentUser]!
  3. Now is: [Now]');
  4.  

":= $" is a replacement for "writestr" => its a minimal bit shorter, but way less expressive. Imho that is like "can we have { for begin"?

"{" and "}" are replacements for ', and ,'
Although syntactically it is this little difference, but logically I see a big difference. WriteStr is logically a concatenation of all parts, while the discussed feature is more a complete string with substituted contents. Of course a programmer can abstract that much, still I guess it does feel different for many people. I'm not advocating for the feature, but if its done it should be done in a good way that doesn't mess with Pascal syntax.

xiyi0616

  • Jr. Member
  • **
  • Posts: 64
I’ve only mentioned ‘format’ before; now, as a beginner, I’ll casually complain about the use of +combined with type conversion functions, just for fun.

Some might feel that
Code: Pascal  [Select][+][-]
  1. age := 3;  
  2. height := 1.7;  
  3. s := 'My age is ' + IntToStr(age) + '  My height is ' + FloatToStr(height) + ' meters.';
  4.  
and
Code: Pascal  [Select][+][-]
  1. age := 3;  
  2. height := 1.7;  
  3. s := $'My age is {age}  My height is {height} meters.';
  4.  

are quite similar, and programmers may be more used to the former. Let me share some thoughts from a programming perspective:

As a user of the Lazarus IDE, my primary goal is getting development tasks done. When I look at code like this, I naturally focus more on business logic. Seeing:

s := $'My age is {age} My height is {height} meters.';

my attention goes toward checking whether the statement expresses the intent correctly and whether the variables are the right ones — without explicit calls like IntToSt rappearing in plain sight. I do know implicitly that a variable is being converted to a string here, but that awareness stays shallow and doesn’t consume mental effort.

In contrast, when I encounter:

s := 'My age is ' + IntToStr(age) + ' My height is ' + FloatToStr(height) + ' meters.';

the repeated pattern ' + IntToStr(...) + 'visually jumps out at me. The information reaches my brain, which then analyzes: “Ah, here a variable is being converted to an string.” That’s programming‑level detail, not business‑logic level. It’s as if my mind, originally engaged in analyzing business requirements, suddenly gets interrupted by a lower‑level implementation concern — a coding‑related interruption. My knowledge has to step in: What is this? Is the syntax correct? Only after that analysis is done can I return to the main thread of business‑logic thinking.

To me, that seems entirely unnecessary. This isn’t what I want to focus on; it’s something the language itself should handle, and shouldn’t appear explicitly in the code.  Don't need to show me the detail.

I know some might think I’m too weak — bothered even by such minor details. Maybe so: I’m like a 386, you’re an i9. But regardless of processing power, unnecessary interruptions and extra cognitive load simply shouldn’t exist.

Just like you reading this now: your brain coordinates muscles around your eyes to move them, coordinates hand muscles to move and click the mouse — yet you don’t need to know exactly how it’s done! You keep your attention where it should be.

As Lazarus users, we also want to keep our focus where it belongs. Some things we’ll just have to rely on those maintaining Lazarus and Free Pascal to handle it well — thanks.


« Last Edit: April 18, 2026, 05:18:43 am by xiyi0616 »

egsuh

  • Hero Member
  • *****
  • Posts: 1790
s := Format('My age is %d,  My height is %f meters.',   [age, height]);

The f`......` style seems to require the format string should be parsed during compilation, or at runtime. Interpreters do that for all statements at runtime. But in the format function, the format string is simply a constant. It seems a small discrepancy of principle.

xiyi0616

  • Jr. Member
  • **
  • Posts: 64
s := Format('My age is %d,  My height is %f meters.',   [age, height]);

The f`......` style seems to require the format string should be parsed during compilation, or at runtime. Interpreters do that for all statements at runtime. But in the format function, the format string is simply a constant. It seems a small discrepancy of principle.

Looking back through the thread, Fibonacci’s earlier explanation seems to indicate that the current $-string handling performs format validation at compile time (From my understanding).

Fibonacci

  • Hero Member
  • *****
  • Posts: 941
  • Behold, I bring salvation - FPC Unleashed
I want to reassure some of you (and probably trigger a few others  8-)): I'm going to merge this feature into unleashed main.

The following already works on my local branch:

Code: Pascal  [Select][+][-]
  1. writeln($'$DEADBEEF as hex: {$DEADBEEF as 'x8'}');
  2. writeln($'1337      as hex: {1337 as 'x4'}');
  3. // without SysUtils: Error: String interpolation format requires function "INTTOHEX" in unit "SYSUTILS" - add it to uses clause
  4. writeln($'Float:            {1234.5678 as '0.00'}');
  5. writeln($'Float:            {1234.5678 as '%.4f'}');
  6.  
  7. writeln($'Nested interpolated strings are {$'working'}');
  8.  
  9. var name := 'Fibonacci';
  10. writeln($'It''s {Now as 'hh:nn'}. {case HourOf(Now) of
  11.   0..4:  $'Shouldn''t you be sleeping, {name}?';
  12.   5..10: $'Good morning {name}!';
  13.  11..20: $'Have a nice day, {name}!';
  14.  21..23: $'G''night, sweet dreams, {name}!';
  15.  else ''
  16. }');

Code: Text  [Select][+][-]
  1. $DEADBEEF as hex: DEADBEEF
  2. 1337      as hex: 0539
  3. Float:            1234.57
  4. Float:            1234.5678
  5. Nested interpolated strings are working
  6. It's 11:05. Have a nice day, Fibonacci!

I'm still thinking about what else could be added or improved, so if you have any suggestions, now is the time - drop them before it lands in main, because after that there will be no changes.
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Warfley

  • Hero Member
  • *****
  • Posts: 2056
The "problem" why something like this is not going to be as useful as it is in e.g. python is because pascal does not have a universal string conversion. In python there is an automatic string conversion for all types and if you need a custom conversion function you can simply overload the str function. In pascal, even going by the rules of "Write", you can only print numbers and strings. No objects, records, arrays, pointers, etc.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12845
  • FPC developer.
The "problem" why something like this is not going to be as useful as it is in e.g. python is because pascal does not have a universal string conversion. In python there is an automatic string conversion for all types and if you need a custom conversion function you can simply overload the str function. In pascal, even going by the rules of "Write", you can only print numbers and strings. No objects, records, arrays, pointers, etc.

You could do duck typing for a .tostring, a virtual method for classes, and advanced records/ record helpers etc. But it is not a natural fit, but that is the case with most of these converted features.

Fibonacci

  • Hero Member
  • *****
  • Posts: 941
  • Behold, I bring salvation - FPC Unleashed
The "problem" why something like this is not going to be as useful as it is in e.g. python is because pascal does not have a universal string conversion. In python there is an automatic string conversion for all types and if you need a custom conversion function you can simply overload the str function. In pascal, even going by the rules of "Write", you can only print numbers and strings. No objects, records, arrays, pointers, etc.

You could do duck typing for a .tostring, a virtual method for classes, and advanced records/ record helpers etc. But it is not a natural fit, but that is the case with most of these converted features.

Nailed it. I've been experimenting exactly along those lines. It's still WIP and I've got a lot to think through - especially around how far to push the automatic JSON dispatch vs. keeping it explicit. Simple demo of what it can do right now.

For classes and advanced records the dispatch order is: if the type has a ToJSON method, that's used; otherwise RTTI-based serialization (via fpjsonrtti); and ToString as the last fallback.

That said, I'm considering dropping the automatic JSON path altogether. It's complicated, it needs a patched fpjsonrtti, it bloats the binary, and frankly I'm not a fan of RTTI to begin with (Unleashed has a no-RTTI mode for a reason). A ToJSON / ToString duck-typing dispatch would still be useful, but the RTTI fallback may not be worth carrying.

Code: Pascal  [Select][+][-]
  1. program app;
  2.  
  3. {$mode unleashed}
  4.  
  5. uses SysUtils, fpjsonrtti;
  6.  
  7. type
  8.   {$M+}
  9.   TPerson = class
  10.   private
  11.     FName: string;
  12.     FAge: integer;
  13.   public
  14.     constructor Create(const AName: string; AAge: integer);
  15.   published
  16.     property Name: string read FName;
  17.     property Age: integer read FAge;
  18.   end;
  19.  
  20. constructor TPerson.Create(const AName: string; AAge: integer);
  21. begin
  22.   FName := AName;
  23.   FAge := AAge;
  24. end;
  25.  
  26. begin
  27.   writeln($'Array 1: {[1, 2, 3, 555]}');
  28.  
  29.   var a := [1, 3, 3, 7];
  30.   writeln($'Array 2: {a}');
  31.  
  32.   var Warfley := TPerson.Create('Warfley', 422);
  33.   writeln($'Object as JSON: {Warfley}');
  34.  
  35.   readln;
  36. end.

Output:

Code: Text  [Select][+][-]
  1. Array 1: [1, 2, 3, 555]
  2. Array 2: [1, 3, 3, 7]
  3. Object as JSON: { "Age" : 422, "Name" : "Warfley" }
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

 

TinyPortal © 2005-2018