Recent

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

Fibonacci

  • Hero Member
  • *****
  • Posts: 560
  • Internal Error Hunter
Re: New language features?
« Reply #120 on: March 10, 2024, 02:06:00 pm »
It wont work if there is an exception inside that repeat block.

440bx

  • Hero Member
  • *****
  • Posts: 4647
Re: New language features?
« Reply #121 on: March 10, 2024, 02:10:02 pm »
It wont work if there is an exception inside that repeat block.
if the code generates an exception then it is not working with or without the repeat block.

Bug-free code doesn't generate exceptions (at least that's the way bug-free code used to be.  I'm old enough to remember those days)
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #122 on: March 10, 2024, 02:59:47 pm »
It wont work if there is an exception inside that repeat block.
if the code generates an exception then it is not working with or without the repeat block.

Bug-free code doesn't generate exceptions (at least that's the way bug-free code used to be.  I'm old enough to remember those days)

I don't like exceptions because they confuse general error handling with a real exception, that is a fault which should end the program entirely. They also have a unique property that they can exit complicated recursive functions without lots of boiler plate code.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11818
  • FPC developer.
Re: New language features?
« Reply #123 on: March 10, 2024, 07:16:29 pm »
(I read the message before, but when Muus referred to it, I realized I hadn't replied.

I have been following this forum for many years and I basically understand the philosophy that underlies the choices on language features to implement.

What is not entirely clear to me is whether today, as in the past, the main goal is still to be compatible with Delphi or not. It seems to me that this project was born over 20 years ago to be an opensource alternative to Delphi. And Delphi mode exists precisely to maximize this compatibility.

True.
Quote
But since Delphi has recently introduced new (and questionable, I agree) features into the language like the ones described here:

https://blog.marcocantu.com/blog/2018-october-inline-variables-delphi.html

Yes.
Quote
which the core developers reject on principle, as they violate the founding philosophy of Pascal, consisting of "declare before using", it seems clear to me that Delphi and FPC have taken different paths.

No, the FPC devels postponed the issue for at least 5 years to avoid repetitive discussions, and see how the lay of the land is then. This pauze is enabled by the fact that new Delphi features are not directly used widely, specially by component builders that often support multiple versions. It doesn't create a new situation because FPC always lagged several versions in dialect.

Quote
This is one of the main problems of object pascal: lack of standardization and proliferation of dialects... The ISO 7185:1990 and ISO/IEC 10206:1991 standards, if I remember correctly, concern classic pascal, not object pascal.

There is a draft from Apple for object pascal, but it is quite different from Borland's. The other pascal standards were not widely adopted, most compilers follow the Borland dialects (TP,Delphi) not the ISO standards.

The standards issue is a classic argument from C/C++ corner, but I never particularly agreed with that view.  I still think the C standards (*) (and the road to them) are special mostly due to Unix history (including Bell labs antitrust situation and the source based distribution of Unix application) and associated US government procurement pressures that lead to a rare set of conditions, and as such are the exception rather than a rule book for arbitrary languages. 

And that includes Pascal even though it happens to have some standards to its name due to existing in the same period when standards were all the rage.

(*) and C++ later mostly targeted the same group and markets and simply fell into the same mould.
« Last Edit: March 11, 2024, 06:57:36 pm by marcov »

wizzwizz4

  • New Member
  • *
  • Posts: 20
Re: New language features?
« Reply #124 on: September 28, 2024, 10:10:06 pm »
Maybe inline vars are the not the solution but the fact Pascal lets you access uninitialized memory outside of the scope in which it's allocated is a design flaw of the language. Because declaration and assignment are disjointed you can never be sure if the memory is actually initialized or not. This is a huge source of potential bugs and crashes. It would be nice for example if you could have pointers which were non-nullable but the design of Pascal prohibits this.

I don't think Pascal's design prevents anything of the sort. Other language implementations manage it. Take Rust:

Code: Text  [Select][+][-]
  1. fn main() {
  2.     let mut x: i32;
  3.     let y: &mut i32;
  4.  
  5.     x = 5;
  6.     while x < 20 { x *= x; }
  7.     if x < 24 {
  8.         y = &mut x;
  9.     }
  10.     *y += 5;
  11.     println!("{}", x);
  12. }

Separate declaration and initialisation, but the rustc compiler will catch it.

error[E0381]: used binding `y` is possibly-uninitialized
  --> src/main.rs:10:5
   |
3  |     let y: &mut i32;
   |         - binding declared here but left uninitialized
...
7  |     if x < 24 {
   |        ------ if this `if` condition is `false`, `y` is not initialized
8  |         y = &mut x;
9  |     }
   |      - an `else` arm might be missing here, initializing `y`
10 |     *y += 5;
   |     ^^^^^^^ `*y` used here but it is possibly-uninitialized


Pascal's actually quite amenable to this, I thought. Some languages (e.g. Rust) are so overcomplicated that you can't practically do anything outside the reference implementation, but for Pascal you should be able to make a standalone static analysis tool that does this. I don't even think you'd need to pay attention to objfpc vs delphi mode. (It's mathematically impossible to make a terminating checker with no false positives or negatives, so any useful implementation will forbid at least one valid program; so there would be backwards-compatibility concerns if you wanted to get this feature in fpc proper.)

I see rumours that Delphi has a compiler hint for this, but I don't have a copy of Delphi so couldn't use it as my example.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #125 on: September 28, 2024, 10:21:49 pm »
if you are concerned, you can have the compiler fail (most?) such code.

E.g.
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. procedure foo;
  4. var a: integer;
  5. begin
  6.   writeln(a);
  7. end;
  8.  
  9. begin
  10.   foo;
  11. end.
  12.  

"a" is not initialized. And you get a warning about it.

Compile with
Code: Text  [Select][+][-]
  1. -Sew
and it will give an error.

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #126 on: September 29, 2024, 12:09:35 am »
There actually is the data flow analysis (dfa) which aims at doing exactly this. I also have worked on something like this in the past.

The dfa is disabled by default, and it is an optimization switch, so it's probably not on the radar for most people, but you can enable it with the -Oodfa switch

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #127 on: September 29, 2024, 03:16:10 am »
I don't think Pascal's design prevents anything of the sort. Other language implementations manage it. Take Rust:

What about stuff like this? list is alive only in the for-loop but you can access it in the main procedure body and crash even. Variables are tied to memory and have scopes in which they are used so what do you gain from declaring it outside of where it's used?

Forcing the user to declare the type outside of where they're used just doesn't make sense from a practical standpoint of how the program runs. I think this is the legacy of the language creators desire to teach and write "correct" programs based on whatever dogma was popular at the time.

Code: Pascal  [Select][+][-]
  1. var
  2.   list: TList
  3. begin
  4.   for i := 0 to 10 do
  5.     begin
  6.       list := TList.Create;
  7.       DoSomething(list);
  8.       list.Free;
  9.     end;
  10.  
  11.   if list.Count > 0 then
  12.     ;
  13. end.
  14.  

uart

  • Jr. Member
  • **
  • Posts: 59
Re: New language features?
« Reply #128 on: September 29, 2024, 04:03:46 am »
Interesting discussion, I'll throw in my non expert opinion.

I can see some pros and cons of both coding styles. For traditional Pascal that's procedure level scope, and for the C++ (or with new Pascal features) it's sub-procedural level scope, such as the for loop in the above example.

The +ive side. Scope is good. Limiting it to just were it's needed is a good thing.

The -ive side. I personally like Pascal's procedural level scope for code readability. Somehow I just find it convenient to have all the variables listed at the top of the procedure when reading someone's code. If you're scoping variables inside other blocks like while and if etc, then those things can be nested it can make it a little more difficult to trace back to exactly where a particular variable is defined.

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #129 on: September 29, 2024, 04:18:24 am »
The -ive side. I personally like Pascal's procedural level scope for code readability. Somehow I just find it convenient to have all the variables listed at the top of the procedure when reading someone's code. If you're scoping variables inside other blocks like while and if etc, then those things can be nested it can make it a little more difficult to trace back to exactly where a particular variable is defined.

I agree but now you have the variables that are accessible outside of where they're used. People know not to declare globals when a local could be used so it's natural this concept should be extended to sub-procedure scopes. There are private/public sections for this reason too, i.e. we're trying to limit the scope in which variables can be accessed.

Having memory accessed outside where it's desired is more important than this nice bookkeeping of pre-declaring variables.

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #130 on: September 29, 2024, 09:41:19 am »
I don't think Pascal's design prevents anything of the sort. Other language implementations manage it. Take Rust:

What about stuff like this? list is alive only in the for-loop but you can access it in the main procedure body and crash even. Variables are tied to memory and have scopes in which they are used so what do you gain from declaring it outside of where it's used?

Forcing the user to declare the type outside of where they're used just doesn't make sense from a practical standpoint of how the program runs. I think this is the legacy of the language creators desire to teach and write "correct" programs based on whatever dogma was popular at the time.
I mean if you feel like you need to scope something, you can always put it in a (nested) procedure.

That said building something that tracks the liveliness of objects by tracking constructor and destructor calls would certainly also be possible to do in a cfg/dfg analysis, even though the usage of free instead of destroy makes it a bit more tricky (aside from the fact that we should stop using free anyhow, but I know myself how hard it is to get rid of learned patterns)
« Last Edit: September 29, 2024, 09:43:23 am by Warfley »

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #131 on: September 29, 2024, 10:34:16 am »
I mean if you feel like you need to scope something, you can always put it in a (nested) procedure.

That said building something that tracks the liveliness of objects by tracking constructor and destructor calls would certainly also be possible to do in a cfg/dfg analysis, even though the usage of free instead of destroy makes it a bit more tricky (aside from the fact that we should stop using free anyhow, but I know myself how hard it is to get rid of learned patterns)

but having the list declared outside where it's used and allocated doesn't serve any purpose except to confuse and cause crashes.

Managed types are another problem entirely. Now the compiler doesn't know where the variable can be used and so it needs to manage it in the outer scope and call used code (initialize/finalize). Even the initialize isn't needed if the assignment was tied to the declaration but this is impossibe if the assignment happens in another part of the program.

I guess you could do complicated static analysis but that's just a mitigation for the underlying problem.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #132 on: September 29, 2024, 10:50:14 am »
The problem with languages that allow finer scoping (declare inside the code for a single block) is that I always have the feeling the lie to me.

Terminologie: "variable is defined" = has a defined value (has been intentionally been assigned the value it currently holds). That is including nil if that was intentionally assigned. (The question as to if/when nil should be allowed is not a scoping issue: Sure, cases can be constructed were lack of scope forces it. But, not all cases can be removed by finer scope.)

First of all: It does not solve the problem. It just shrinks it, so it is less visible.

Even if I take a loop, or wrap any arbitrary bit of code into a begin/end to create a scope: A variable is not necessarily defined at all places in that code (that requires additional work on top of scope).
- "not necessarily" It can be in some cases, but it wont be in all. Hence "shrinking, not solving"
- You could have code with lots of different if/then, and initialization happening inside each conditional block (or does not happen).
- If your answer is, that the var must be assigned when declared => that is not scope related. That could be done (if implemented) in the current procedure wide declaration too, have an initial value for each declared local var / so: not scope)

And then you can't even always define scopes as little as you would need. Scopes for different variables may overlap. begin/end can only be nested, but not overlap.

So in the end, if I look at a language with an inline declared variable, I always face the question: Has the author taken care of it "defined state". Is it defined in all code paths? Even if this var is initialized at declaration (which is not a scope related feature), can I be sure that it at no time is assigned a value that was not defined?
And here defined has to be taken one level further. "Valid". E.g. for an array index: a value that is in the range of the array size. And it may not be enforceable by type either, if the array has variable size.

And the question of "valid" means, that even if all variables must be assigned at the time of declaration (again, not a scope issue), that does not make the value "valid". And at the end of the day, "valid" is what I am concerned about. "defined" alone is not good. It may be good in a subset of cases, but that means "shrinking, not solving".

So as I said: at the end of the day, if a variable is declared inline, I still have to verify if it is valid at the place where it is accessed. I still have to either trust the Author that they made sure of it (checked the entire scope in which they declared it) or I have to check it myself.
Only argument here is that if I like procedures that are 1000 lines long, then I can use sub-scopes to reduce the code I need to check. But maybe instead of helping with overlong procedures, it should be encouraged to write code in smaller procedures to begin with?


And then the real anti-example is (older) javascript. https://eslint.org/docs/latest/rules/no-use-before-define
They had to have a linter to make sure that inline declarations did not make it worse.


In the end, any code needs to be written and read with care.

IMHO the best way in any language to allow tracking of a variable is to keep the method in which it exists as short as possible. And if that is done, then sub-scopes are just complexity that is not actually needed.


And yes, the famous loop-counter after loop. Well it is the same as any other var that gets initialized only in a subset of the paths the code can take through some branches.
How much would you really gain, if that one particular example would be changed, and the compiler would somehow prevent it. The general problem would still exist. And it would exist with any level of scoping.

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #133 on: September 29, 2024, 11:31:49 am »
but having the list declared outside where it's used and allocated doesn't serve any purpose except to confuse and cause crashes.
Well as I said, you can scope whenever you like:
Code: Pascal  [Select][+][-]
  1. proceudre MyScoped;
  2. var
  3.   i: Integer;
  4.  
  5. procedure SubScope;
  6. var
  7.   list: TList;
  8. begin
  9.   // Do something with i and list
  10. end;
  11.  
  12. begin
  13.   // Prefix
  14.   for i:=0 to count do
  15.     SubScope;
  16.   //postfix
  17. end;

I mean this is of course a very constructed example (as is the nature of examples), but I would argue, if your function is so complex that tracking scope becomes a problem and a real risk for bugs, it's a sign that you shold break up your function into smaller sub functions (thats why we have nested procedures, they are perferct for that)
Managed types are another problem entirely. Now the compiler doesn't know where the variable can be used and so it needs to manage it in the outer scope and call used code (initialize/finalize). Even the initialize isn't needed if the assignment was tied to the declaration but this is impossibe if the assignment happens in another part of the program.
But on the other hand having multiple finalize blocks also adds complexity. Each finalize block is a longjmp entry in a linked list. Imagine this code:
Code: Pascal  [Select][+][-]
  1. procedure test;
  2. var
  3.   s: String;
  4. begin
  5.   //...
  6.   if condition then
  7.   begin
  8.     var l: String;
  9.   end;
  10.   // ...
  11. end;
If scoped initialize/finalize would be used this would be:
Code: Pascal  [Select][+][-]
  1. procedure test;
  2. var
  3.   s, l: String;
  4. begin
  5.   initialize(s);
  6.   try
  7.     //...
  8.     if condition then
  9.     begin
  10.       initialize(l);
  11.       try
  12.         //...
  13.       finally
  14.         finalize(l);
  15.       end;
  16.   end;
  17.   // ...
  18.   finally
  19.     finalize(s);
  20.   end;
  21. end;
Each finally is a longmp, losing all benefits from cache locality and predictive execution. Especially if we consider string here, where if it is never assigned, it is nil, and therefore no memory allocation is performed, the loss of performance through the loss of cache locality will be probably orders of magnitude worse than the "unessecary" initialize and finalize if the condition is false.

So if this is actually an optimization is something that I personally would question and I think needs to be considered for each use case individually
I guess you could do complicated static analysis but that's just a mitigation for the underlying problem.
But thats what static analysis is for. The programmer should not be tasked with micropotimizations (which this is), the programmer should write the code they are most comfortable with and the compiler should make sure the most optimal result is produced. And liveness analysis is not really "complicated" tbh. like there are complicated and actually unsolvable edge cases, but the vast vast majority is very simple

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #134 on: September 29, 2024, 11:42:14 am »
but pulling code out into functions is a hack around an intentional complier limitation. So your choices are breaking inlined code or have access to variables that are freed. It's like we don't care about locality of accessing memory. Why have private/public sections even then? very hard to understand the benefits here.

As for the other issue that's interesting.Why is a long needed? I'm even confused about the linked list. Doesn't is just inject the code to be called statically?

 

TinyPortal © 2005-2018