Recent

Author Topic: New language features?  (Read 30353 times)

BeniBela

  • Hero Member
  • *****
  • Posts: 959
    • homepage
Re: New language features?
« Reply #30 on: June 21, 2023, 12:38:03 am »
Or

Code: Pascal  [Select][+][-]
  1. yList.Sort(function a, b do a < b)
  2.  

Curt Carpenter

  • Hero Member
  • *****
  • Posts: 747
Re: New language features?
« Reply #31 on: June 21, 2023, 05:34:14 am »
Maybe
Code: Pascal  [Select][+][-]
  1. yList.Sort(with a, b do a < b)
  2.  

Makes sense and nicely readable.

Awkward

  • Full Member
  • ***
  • Posts: 154
Re: New language features?
« Reply #32 on: June 21, 2023, 05:46:41 am »
Maybe
Code: Pascal  [Select][+][-]
  1. yList.Sort(with a, b do a < b)
  2.  

Makes sense and nicely readable.
really? and what it means? and are you sure what this is still pascal?

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1315
Re: New language features?
« Reply #33 on: June 21, 2023, 09:29:31 am »
I once worked on a largisch C#.NET application, where one of the senior devs spend his time refactoring the code with Resharper: check all the boxes and let it rip. At some point, it replaced all my nicely readable functions with single, unreadable LINQ statements.

That's also a pet peeve: keep the syntax of your language the same, don't introduce a new one for each new extension. You have to switch your brain to the other syntax all the time, that is tiring. And while doing so, it dissolves into multiple, separate blocks. You tend to lose track of the big picture.

Then again, there are probably FPC users who would love to write their code purely in lambda's, so who am I to disagree. But be cautious with introducing things like this. "Just because we can, and others do the same" is not a good reason in my book.

Curt Carpenter

  • Hero Member
  • *****
  • Posts: 747
Re: New language features?
« Reply #34 on: June 21, 2023, 08:19:46 pm »
<snip>
really? and what it means? and are you sure what this is still pascal?

It may be at some point Awkward, in which case I just like the "with" syntax over the others suggested for readability. 

Coincidentally, I note that author Marco Cantu discourages the use of "with" now in it's usual applications, so it may be a reserved word more open to some refinements where a term like "function" is already heavily burdened in the language (again IMHO).       

Thaddy

  • Hero Member
  • *****
  • Posts: 18935
  • Glad to be alive.
Re: New language features?
« Reply #35 on: June 21, 2023, 08:56:49 pm »
Coincidentally, I note that author Marco Cantu discourages the use of "with" now i   
He has done so for the past 25 years and warned the compiler developers of the scoping issues.
He also warned in his books. (maybe not the early ones)
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

Awkward

  • Full Member
  • ***
  • Posts: 154
Re: New language features?
« Reply #36 on: June 21, 2023, 09:18:00 pm »
Proper using of "with" statement can make sources at least more compact and easy to read.

Leledumbo

  • Hero Member
  • *****
  • Posts: 8836
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: New language features?
« Reply #37 on: June 21, 2023, 09:32:26 pm »
Proper using of "with" statement can make sources at least more compact and easy to read.
Better implementation of with is what I hope, like what Modula-3 does, which is simply an aliasing within the scope of with statement block only. It's quite free from scoping errors (well, unless the alias shadows some outer variables). In Pascal way, it can be done with actual local variable, just hope the compiler understands that you use it simply for aliasing and removes the allocation in the generated code.

Warfley

  • Hero Member
  • *****
  • Posts: 2049
Re: New language features?
« Reply #38 on: June 21, 2023, 10:33:02 pm »
Proper using of "with" statement can make sources at least more compact and easy to read.
The problem with "with" in it's current form in Pascal is, that it was introduced before OOP became the norm. Today in a modern OOP application you are always within an method and it's implicit scope. E.g. when you write a LCL GUI application, you most likely find yourself in a Method of a TForm inherited class. This means you already have alot of methods and properties in that will be implicetly referenced.
Pre OOP you had a handful of global variables, your parameters and local variables, generally speaking not so much going on. The potential on shadowing is very low. But now when you are in a method there are dozens of functions or properties you may shadow when you open a with.

This gets worth when you deal with classes that share a common ancestor. For example I've found code like this quite often:
Code: Pascal  [Select][+][-]
  1. with TButton.Create(self) do
  2. begin
  3.   Parent := Self;
  4.   Caption := ButtonCaption;
  5.   Left := ButtonPos.X;
  6.   Top := ButtonPos.Y;
  7.   OnClick := @ButtonClick;
  8.   Tag := ButtonID;
  9. end;
Note that every single property of the button that is set here, is also a property of a Form that is shadowed by the with block. So if you do an innocent little error, e.g. you simply forgett the begin-end, this code will compile just completely fine, but instead of configuring your Button, it will configure the Form it is called on.

Simultaniously, let's say you have code that computes the ButtonPos, that is based on properties of the Form:
Code: Pascal  [Select][+][-]
  1. ButtonPos := ScreenToClient(CursorPos);
  2. ...
  3. With TButton.Create(Self) do
  4.   // code from above
Now during refactoring you decide that you want this computation to be close to where it is used, so you move it just before the setting of Left and Top:
Code: Pascal  [Select][+][-]
  1. with TButton.Create(self) do
  2. begin
  3.   ...
  4.   ButtonPos := ScreenToClient(CursorPos);
  5.   Left := ButtonPos.X;
  6.   Top := ButtonPos.Y;
  7.   ...
  8. end;

It's the exact same code, just moved down a few lines, but because the with shadows the ScreenToClient method of your Form with that of the button, the code that worked well just before now doesn't work anymore.

These kinds of bugs are often hard to find, and also hard to avoid. Basically at any point in time you need to know all the methods and properties of 1. the thing you are withing as well as 2nd. the class you are in. So unlike before, where you had a few global variables, and used with on a record with a handfull of fields, this is no problem. But now we are talking about classes with up to hundreds of properties and methods (just checked the documentation for TControl, it has over 400 properties and methods).

Also note that a general problem with with is, that you actually can't reference the object you withed directly, as there is no "with self". So you can't call functions with it as parameter, you can only access members (properties, methods or types). So when it was developed in procedural times, it was actually used not for temporary objects, as it is used often today, but it was used for variables when you were to lazy to write out the variable name.

Thats why more recent approaches in languages that want to incorporate with, have gone a different route, where with is intended to scope temporary objects. Things like this:
Code: Pascal  [Select][+][-]
  1. with TButton.Create as btn do
  2. begin
  3.   btn.Parent := Self;
  4.   btn.Caption := ButtonCaption;
  5.   ...
  6. end;
This still enables to write more concise and more readable code, and not to introduce a function wide variable for some minute things, while also solving both problems, 1. you don't have any shadowing and 2. you can pass the temporary object around through the temporary variable "btn".

Another attempt I have seen to solve this without introducing a new variable is by adding a . For the scoping
Code: Pascal  [Select][+][-]
  1. with TButton.Create do
  2. begin
  3.   .Parent := Self;
  4.   .Caption := ButtonCaption;
  5.   ...
  6. end;
This way you can also avoid shadowing by keeping the original essence of the with statement
« Last Edit: June 21, 2023, 10:42:26 pm by Warfley »

Awkward

  • Full Member
  • ***
  • Posts: 154
Re: New language features?
« Reply #39 on: June 21, 2023, 10:43:10 pm »
why you call standard feature as "bug"? Your example looks like NOT bug but PROGRAMMER error. That the different things. i am sure, if you will force your memory, you can remember several similar errors without "with" statement at all.

Warfley

  • Hero Member
  • *****
  • Posts: 2049
Re: New language features?
« Reply #40 on: June 21, 2023, 10:57:45 pm »
Yes it is a mistake, but it's a preventable mistake. Look at it like hardened glass in a phone. It's there in case you drop your phone. Surely that's a stupid idea, why would anyone drop their phone, it's a mistake, no one truly needs hardened glass if they simply don't drop their phone.

But here's the thing, mistakes happen, and as the phone manufacturers build in hardened glass to make it less likely to break when that mistake is going to happen, the same way a programming language should make it easy to avoid errors due to mistakes.

More modern takes on with acknowledge it's shortcomings and harden it against them.
E.g. take the following
Code: Pascal  [Select][+][-]
  1.     with TButton.Create(Self) do
  2.     begin
  3.       .Parent := Self;
  4.       .Caption := ButtonCaption;
  5.       ...
  6.     end;

This can do literally everything that pascal with can, but it completely solves the shadowing issue. There is literally no drawback to this way of implementing with over the pascal way. It does the exact same thing but is less likely for your mistakes to result in a bug. It is strictly better

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: New language features?
« Reply #41 on: June 22, 2023, 11:27:52 am »
Inline variables (constants) and type inference solve this more versatile and elegant:

Code: Pascal  [Select][+][-]
  1.   begin
  2.      const tmp = TButton.Create(Self);
  3.      tmp.Parent := Self;
  4.      tmp.Caption := ButtonCaption;
  5.       ...
  6.   end;
  7.  

The first high level language, which I learned more than 40 years ago, after some BASIC variants, was PEARL, a realtime language derived from ALGOL.
It had inline variables and reference variables.
When I had to learn PASCAL for the job, I missed this badly.

Of course I liked Objects, Turbovision and the Debugger, and Editor, this was much more advanced.
« Last Edit: June 22, 2023, 12:24:38 pm by Peter H »

Warfley

  • Hero Member
  • *****
  • Posts: 2049
Re: New language features?
« Reply #42 on: June 22, 2023, 12:21:01 pm »
Inline variables (constants) and type inference solve this more versatile and elegant:

Code: Pascal  [Select][+][-]
  1.   begin
  2.      const tmp = TButton.Create(Self);
  3.      tmp.Parent := Self;
  4.      tmp.Caption := ButtonCaption;
  5.       ...
  6.   end;
  7.  

The first high level language, which I learned more than 40 years ago, after some BASIC variants, was PEARL, a realtime language derived from ALGOL.
It had inline variables and reference variables.
When I had to learn PASCAL for the job, I missed this badly.

Pascal does not have inline variables not because when Wirth developed Pascal it was not an option, it was specifically chosen not to be included because having a clear seperation between declarations and statements makes the code more readable.

Your code example shows one of the problems with it:
Code: Pascal  [Select][+][-]
  1.   begin
  2.      const tmp = TButton.Create(Self);
  3.      tmp.Parent := Self;
  4.      tmp.Caption := ButtonCaption;
  5.       ...
  6.   end;
  7.  
the "const name = value" syntax is normally used for untyped consts, i.e. simple compiletime substitution. So applying the same logic here, what you wrote should be translated to:
Code: Pascal  [Select][+][-]
  1.      TButton.Create(Self).Parent := Self;
  2.      TButton.Create(Self).Caption := ButtonCaption;
  3.  
But it doesn't, what actually happens is that this is a static lifetime variable, i.e. a writable const, so it is actually equivalent to:
Code: Pascal  [Select][+][-]
  1. const tmp: TButton = nil;
  2. begin
  3.   if not Assigned(tmp) then
  4.     tmp := TButton.Create(Self);
  5.   tmp.Parent := Self;
  6.   tmp.Caption := ButtonCaption;
  7. end;
  8.  

Or to put it in another way, this:
Code: Pascal  [Select][+][-]
  1. procedure Foo;
  2. const x = 42;
  3. begin
  4.  
  5. end;
And this
Code: Pascal  [Select][+][-]
  1. procedure Foo;
  2. begin
  3.   const x = 42;
  4. end;
Are two conceptually completely different constructs with completely different semantics. even though they have the same syntax. A language should always be easy to disambiguate, if something has the same syntax, it should follow the same semantics, if it has a different syntax different semantics, simple as that.

Note that even though C supports inline variables, it is quite common to see in larger projects that they have style guidelines where non temporary variables (like loop iterators) should always be declared at the start of the function, like it is enfroced by Pascal. The linux kernel for example follows this philosophy, here is some code currently powering my phone:
Code: C  [Select][+][-]
  1. void thread_group_cputime(struct task_struct *tsk, struct task_cputime *times)
  2. {
  3.         struct signal_struct *sig = tsk->signal;
  4.         u64 utime, stime;
  5.         struct task_struct *t;
  6.         unsigned int seq, nextseq;
  7.         unsigned long flags;
  8.  
  9.         /*
  10.          * Update current task runtime to account pending time since last
  11.          * scheduler action or thread_group_cputime() call. This thread group
  12.          * might have other running tasks on different CPUs, but updating
  13.          * their runtime can affect syscall performance, so we skip account
  14.          * those pending times and rely only on values updated on tick or
  15.          * other scheduler action.
  16.          */
  17.         if (same_thread_group(current, tsk))
  18.                 (void) task_sched_runtime(current);
  19.  
  20.         rcu_read_lock();
  21.         /* Attempt a lockless read on the first round. */
  22.         nextseq = 0;
  23.         do {
  24.                 seq = nextseq;
  25.                 flags = read_seqbegin_or_lock_irqsave(&sig->stats_lock, &seq);
  26.                 times->utime = sig->utime;
  27.                 times->stime = sig->stime;
  28.                 times->sum_exec_runtime = sig->sum_sched_runtime;
  29.  
  30.                 for_each_thread(tsk, t) {
  31.                         task_cputime(t, &utime, &stime);
  32.                         times->utime += utime;
  33.                         times->stime += stime;
  34.                         times->sum_exec_runtime += read_sum_exec_runtime(t);
  35.                 }
  36.                 /* If lockless access failed, take the lock. */
  37.                 nextseq = 1;
  38.         } while (need_seqretry(&sig->stats_lock, seq));
  39.         done_seqretry_irqrestore(&sig->stats_lock, seq, flags);
  40.         rcu_read_unlock();
  41. }
As you can see, all the variables are defined upfront and inline variable definitions are not used at all, even though C allows them

If you come into a situation where you think that you need to hide your local variable within a scope this is usually a sign that you have too much going on in your function to begin with. For example the Linux Kernel Developer Style Guide says:
Quote
Another measure of the function is the number of local variables.  They
shouldn't exceed 5-10, or you're doing something wrong.  Re-think the
function, and split it into smaller pieces.  A human brain can
generally easily keep track of about 7 different things, anything more
and it gets confused.  You know you're brilliant, but maybe you'd like
to understand what you did 2 weeks from now.
If you only have 5-10 variables, there shouldn't be any need to hide them within scopes.

This is different in languages where scope is associated with lifetime, e.g. in C++:
Code: C  [Select][+][-]
  1. {
  2.   std::ofstream fs;
  3.   fs << "Hello File!";
  4. } // End of scope will call destructor of FS
Or in Python:
Code: C  [Select][+][-]
  1. with FileOpen('Filename', 'w') as fs: # As soon as with is over the file will be closed
  2.   fs.Write('Hello File');

But at least right now Pascal does not have scoped lifetime, so I don't see the need for scoped variables for that purpose. That said, I think there is one key usecase, that is iterator variables:
Code: Pascal  [Select][+][-]
  1. for var i: Integer = 0 to 100 do
  2.   ...
Because they are so strictly tied to the loop context that it does not make sense to define them outside
« Last Edit: June 22, 2023, 12:37:04 pm by Warfley »

Thaddy

  • Hero Member
  • *****
  • Posts: 18935
  • Glad to be alive.
Re: New language features?
« Reply #43 on: June 22, 2023, 01:01:59 pm »
The separation of declaration and use are sacret to Pascal and for good reason: inline declaration of variables leads to less maintainable code. It is simply a bad feature and I am glad the core dev's decided not to implement this. Not a majority but 100%.

Since all of those also us C an derivatives they are fully aware of writing unmaintainable code, especially in teams. Such a feature is bad squared.
« Last Edit: June 22, 2023, 01:04:27 pm by Thaddy »
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

Peter H

  • Sr. Member
  • ****
  • Posts: 272
Re: New language features?
« Reply #44 on: June 22, 2023, 02:19:35 pm »
I have tried in Delphi:

Delphi supports this:

Code: Pascal  [Select][+][-]
  1.   begin
  2.     var x:=1;
  3.   end;
  4.  
  5.   var x:integer;
  6.   x:=2;
  7.  

It does not support this:

Code: Pascal  [Select][+][-]
  1.  
  2.   var x:integer;
  3.   x:=2;
  4.  
  5.   begin
  6.     var x:=1;
  7.   end;
  8.  
  9.  

This is very strange halfbaken language design, if it is not a bug. I did not know this.
« Last Edit: June 22, 2023, 02:24:09 pm by Peter H »

 

TinyPortal © 2005-2018