Recent

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

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #135 on: September 29, 2024, 11:59:58 am »
but pulling code out into functions is a hack around an intentional complier limitation.

That isn't what Warfley wrote. At least not, if I read him correctly.

He wrote
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
Code of such complexity will still be that complex if you declare variables inline. It may even be considered more complex, due to the need to track which variable was declared where (but that last bit may depend on various factors)

Independent of having inline declarations or not, code of such complexity should be simplified. Always.
(So that has nothing to do with the inlining of declarations.)

And if your code isn't that complex to begin with, then inline declarations really don't benefit you much. (read my post, they don't proof anything about variables being "defined" or "valid", they just reduce the amount of code that you need to read in order to check that they are / But if your code never is that complex, then you don't need to reduce it...)

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #136 on: September 29, 2024, 12:40:37 pm »
On code complexity and inline variables, it can literally just be that one function I wrote which is hard to make simpler.

The problem is memory accessibility. I've used Pascal forever along with other languages that all have inline variables and today I struggle to understand why I want to be able to access variables out scope in which they are allocated. The original notion of proper structured programming just hasn't held up over time for me.

Here's the example with inline vars. Imagine it's just this one function, nothing more. Methodology aside the benefits compared to the previous example:

1) No need to write and type list and TList twice.
2) list can not be accessed outside the scope it's alive so less chance for crashes.
2.1) If I had a function like CreateList I wouldn't need to go find the return type and redeclare it and possibly import a unit just for the declaration.

Benefits of the traditional Pascal are what, proper methodology? I have to type more, introduced a vector for crashing and maybe needed to import a unit and search for type names. I struggle to justify that loss for something non-tangible. I'm not anti-Pascal obviously but I think it lost the argument on this matter, for me at least.

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

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #137 on: September 29, 2024, 01:04:21 pm »
On code complexity and inline variables, it can literally just be that one function I wrote which is hard to make simpler.

Well it can, just move TList inside DoSomething, there is absolutely no reason to pass something as parameter if it is never used outside. That makes it much simpler.

The problem is memory accessibility. I've used Pascal forever along with other languages that all have inline variables and today I struggle to understand why I want to be able to access variables out scope in which they are allocated. The original notion of proper structured programming just hasn't held up over time for me.
Simple, inline variables incentivise nesting functionality by hiding complexity in the code. Whenever I add a variable to a function header I think to myself "I already have so many variables, maybe I should split up the function", which is exactly what you want you programmers to do.

This is exactly why the Linux Kernel even though it uses C, which does have inline variable definition, always uses prefixed variable definitions, because as part of the style guid it states that you should limit the number of local variables:
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.
Which is why you should put them at the top of the function, so you can easily keep track of them.

If you are at the point where inline variables become useful, you should be restructuring your function
« Last Edit: September 29, 2024, 01:07:32 pm by Warfley »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #138 on: September 29, 2024, 02:47:29 pm »
Here's the example with inline vars. Imagine it's just this one function, nothing more. Methodology aside the benefits compared to the previous example:

1) No need to write and type list and TList twice.
But list could need to be of class TBaseList. As I reader I want to know for sure that it is of class TList. (the variable, not the instance stored in it).

Why?
Because if "DoSomething" was "procedure DoSomething(VAR l: TBaseList);" then that is needed. 

Of course, you can add "yet another rule" to the conditions under which an inferred type is sufficient, and a declaration can be skipped. But being a strong typed language that rule should already state "never". (anyway typing inferring isn't about scope)


Quote
2) list can not be accessed outside the scope it's alive so less chance for crashes.

Yes, but the loop could have further statements after the "list.free". So the exact same problem still exists. There is nothing gained. You still have the exact same problem.  You just found a way to pretend to less experienced readers that this code is now safe(r). But it isn't. Hence I wrote that this kind of declaration to me looks like a lie.

Quote
2.1) If I had a function like CreateList I wouldn't need to go find the return type and redeclare it and possibly import a unit just for the declaration.
All of which Codetools do for you, if you want.

- And again "typing inferring" isn't scope. (you could just have typeless vars in the current "var" section / I would NOT want this, but it would allow inferring)
- And "typing inferring" brings other issues (such as I described above)

Anyway, I wonder why your argument for inline declaration have wandered from scoping to "typing inferring". (Pretending that inline declaration is a prerequisite for typing inferring)?

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





Quote
Benefits of the traditional Pascal are what, proper methodology? I have to type more, introduced a vector for crashing and maybe needed to import a unit and search for type names. I struggle to justify that loss for something non-tangible. I'm not anti-Pascal obviously but I think it lost the argument on this matter, for me at least.

The extra "type work" and lookup are "strong typing".
- The declaration expresses an indent and helps the reader.
- Looking up the type can save you from nasty surprises

Code: Pascal  [Select][+][-]
  1. for var i {type to be infered} := 0 to SomeCount - 1 do writeln(i);

Looks harmless, doesn't it?

And you saved yourself the time to look up what type SomeCount has. And that is the type the compiler may infer. (If you think it should infer it from the constant 0, then replace that with "StartVal" of the same type as SomeCount)

Here is the full code, adapted to compile with current fpc.
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. var SomeCount, i: cardinal;
  3. begin
  4.   {$R-}{$Q-}
  5.   SomeCount := 0;
  6.   for i {type to be infered} := 0 to SomeCount - 1 do writeln(i);
  7. end.
  8.  

Just run it, and watch how it keeps counting.

Of course in real code the bug caused by such type inferring would be much harder to find. Happy bug hunting. 
« Last Edit: September 29, 2024, 02:52:38 pm by Martin_fr »

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #139 on: September 29, 2024, 03:12:30 pm »
First off, what would happen if "for var i := 0 to SomeCount - 1 do" always inferred i as Integer? It's possible I've never once used another type for an iterator. Not having to declare i over and over again would be nice too.

As for "statements after list.free", sure that's a perennial problem is accessing freed memory which you still have but the scope nonetheless restricted even further to the problem area. Seems like a win to me.

I only brought up type inferring because it's another benefit of inline vars. Untyped vars in the declaration section wouldn't make sense to me because then what is their size? You could assign anything to them at a later time so I don't see how that works.

Also don't forget Pascal infers types for constants so it's not that unnatural of an idea. Sure it's not exactly the same because they are converted upon assignment so if you want a specific type you can supply that.

I would never advocate for JavaScript syntax with no types at all. Just in obvious situations where you know the type and you don't want to change it like "var list := TList.Create". You know it's TList so adding the type is just repetition to my eyes. Even for function results if you need to declare the variable usually then you know the type before calling it and thus hiding it _may_ not a problem for readability. Most of the time the declaration are above and don't see them anyways.


Thaddy

  • Hero Member
  • *****
  • Posts: 15933
  • Censorship about opinions does not belong here.
Re: New language features?
« Reply #140 on: September 29, 2024, 03:19:38 pm »
If I smell bad code it usually is bad code and that includes my own code.

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #141 on: September 29, 2024, 03:25:09 pm »
Tuples interesting, but not a real compiler feature. 😔 This shows the need for a variable number of generic parameters too. I tried to make code like that before and it was painful.

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #142 on: September 29, 2024, 03:25:55 pm »
First off, what would happen if "for var i := 0 to SomeCount - 1 do" always inferred i as Integer? It's possible I've never once used another type for an iterator. Not having to declare i over and over again would be nice too.
Integer doesn't make sense, like in most cases when you iterate arrays, lists, etc. you probably want SizeInt. That Lazarus always infers Integer when doing auto completion (ctrl+shift+c) is IMHO a problem. The days of 32 bits are over and today you can easily have lists or arrays with more than a billion elements.

I only brought up type inferring because it's another benefit of inline vars. Untyped vars in the declaration section wouldn't make sense to me because then what is their size? You could assign anything to them at a later time so I don't see how that works.
Simple, first assignment sets type. Thats very trivial. Also don't forget inline Variables are only inline at the high level language, in the assembly they are allocated on the stack frame when the function is entered, so all variables will in the end be technically allocated when the compiler enters the function.

Also don't forget Pascal infers types for constants so it's not that unnatural of an idea. Sure it's not exactly the same because they are converted upon assignment so if you want a specific type you can supply that.
Which already is a headache when you start working with ImplicitSpecialization for generics, and use untyped consts, because suddenly it makes what you think should be an Integer to a ShortInt, because it's a small constant, and suddenly all your code breaks because you assumed Integer and get overflows and downcasts everywhere.

Just today I again had a bug where I wrote a float constant into a double and checked it against the constant afterwards and it failed because turns out the constant was typed extended and therefore had different rounding.

Typeinference for complex types can be neat, especially if you work with a lot of generics, and having to type "specialize Type1<String, specialize Type2<Integer>>" all the time, but as soon as there is any ambiguity, like with Integer types or Float types, or hell even Char or String, it becomes a mess
« Last Edit: September 29, 2024, 03:34:58 pm by Warfley »

Warfley

  • Hero Member
  • *****
  • Posts: 1664
Re: New language features?
« Reply #143 on: September 29, 2024, 03:31:43 pm »
Tuples interesting, but not a real compiler feature. 😔 This shows the need for a variable number of generic parameters too. I tried to make code like that before and it was painful.
Lol ask me how much fun it was to write the tests for it xD

It's most painful with function pointer types, for my Iterator Library I had to define generic types for all kinds of different functionpointers (procedures, nested, methods, const and non const). For this I actually wrote a python script to generate the FPC code.
This should be very much alleviated with function references, but right now it's not usable because they cannot deal with generic returns but I hope this gets fixed soon so I can start using those instead

PS:
Code: Pascal  [Select][+][-]
  1.     This file is part of the Free Pascal run time library.
  2.     Copyright (c) 2001-2005 by the Free Pascal development team
  3.  
2001-2005 and "Free Pascal development team", guess someone just copy pasted lol  :D
« Last Edit: September 29, 2024, 03:36:43 pm by Warfley »

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #144 on: September 29, 2024, 03:43:09 pm »
The constants with implicit specialization are indeed a problem. I would at least prefer to upcast ordinals to Integer. I don't think I asked the compiler team so I'm not sure if they'd approve of that. Btw, I thought Integer was 64 bit on 64 bit systems but if not then SizeInt. It's no good passing "1" assumes the calling function wants a ShortInt.

How inline variables worked I thought is that they alloc the type on the stack at the top of the function then perform the assignment where they were used so it's no different from normal variables there.

The function references are now a stand in for a long needed "fat function pointer" which can handle any type (global, method, nested) but they come with an overhead of allocating and reference counting. I really wish they'd give us the same flexible type for cases where you don't need it to survive the scope and can't afford the overhead. As you learned doing it with code is a mess and never truly complete. Worse code too I'm sure in the end.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #145 on: September 29, 2024, 04:26:28 pm »
First off, what would happen if "for var i := 0 to SomeCount - 1 do" always inferred i as Integer? It's possible I've never once used another type for an iterator. Not having to declare i over and over again would be nice too.

If start is qword($8f00 0000 0000 0001) and end is qword($8f00 0000 0000 0007), then that would not work with integer. Imagine something looping from a start-address to an end-address. (in virtual address space, otherwise those addresses don't really come up / but I have seen kernel addresses in the range $7ffff .................... .

Quote
As for "statements after list.free", sure that's a perennial problem is accessing freed memory which you still have but the scope nonetheless restricted even further to the problem area. Seems like a win to me.

Well, to me only if the scope was unreasonable big before. And then that problem also happens a lot outside of loops, where it is often much harder (or impossible) to limit the scope. So the "gain" (if any) is really only limited to a very specific subset of code.

Where as one could argue that in general the declare statements add code into the body, increasing its complexity (to the reader). So there even can be seen a loss.

Quote
I only brought up type inferring because it's another benefit of inline vars. Untyped vars in the declaration section wouldn't make sense to me because then what is their size? You could assign anything to them at a later time so I don't see how that works.
The inferring would happen when they are used/assigned the first time. It would then stick for the rest of the code.  That is pretty much the same as what your example does. Only that the "reservation of the name" happens in a diff place. (and the scope changes / but that is unrelated to the inferring of the type)

Technically at least they do the same. Subjective there may be preferences.

Quote
Also don't forget Pascal infers types for constants so it's not that unnatural of an idea. Sure it's not exactly the same because they are converted upon assignment so if you want a specific type you can supply that.
I can't find the documentation....
But I think I read somewhere: numbers (123) have a type. That is hardcoded. So yes its converted when assigned to a variable. And I think (and that can be seen as questionable) that type may depend on representation (hex is unsigned, or isn't it ... really need to find the doc for it).

and the "const MYFOO = 1". well it isn't an assignment. ":=". it makes MYFOO a placeholder for "1". So then that has the same rules for its type.

But as I said, couldn't find the docs now.  I may be wrong on this. Long way down memory lane.


Quote
I would never advocate for JavaScript syntax with no types at all. Just in obvious situations where you know the type and you don't want to change it like "var list := TList.Create". You know it's TList
But do you? I don't. It could be TObject for some reason.  (I gave one earlier).

Of course, if it needed to be, it could be specified then and only then. But talk about making things simpler, if that means you get a complex rule set when you can have the type inferred and when not...

Another example: maybe that list should be an COM interface type? So lifetime is managed? So then when a class implement an interface (com interface) should the compiler always infer that interface type? Because that saves code down the line, doesn't it.

Also if you infer from factory methods, or from "type TFooClass = class of something" => then the type can change. And I know your reply will probably be, that inferring is perfect for that? Because it will follow that change....
Only if that new type does break the code.... And I don't mean in the sense of break compilation, that is easy to fix.
But it changes the functionality. Maybe something down the line called an overloaded function, or relied on a type helper? The new inferred type still finds something that it can be compiled, but it does something completely different....
Well again, happy bug hunting.


alpine

  • Hero Member
  • *****
  • Posts: 1268
Re: New language features?
« Reply #146 on: September 29, 2024, 04:27:06 pm »
Integer doesn't make sense, like in most cases when you iterate arrays, lists, etc. you probably want SizeInt. That Lazarus always infers Integer when doing auto completion (ctrl+shift+c) is IMHO a problem.
Agreed, SizeInt is a lot more reasonable.

The days of 32 bits are over and today you can easily have lists or arrays with more than a billion elements.
Not true at least if you want to develop embedded apps. I'm using an Intel-386 compatible SoC. Not to mention STM32, ESP32, PIC32, AVR, etc.

Simple, first assignment sets type. Thats very trivial. Also don't forget inline Variables are only inline at the high level language, in the assembly they are allocated on the stack frame when the function is entered, so all variables will in the end be technically allocated when the compiler enters the function.
Incorrect, inline variables are about lifecycle too, not only for the HL visibility rules. They must be created/destroyed immediately after entering/leaving their scope/compound, otherwise they will be equally useless, imagine auto_ptr<> equivalent.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Ryan J

  • Full Member
  • ***
  • Posts: 138
Re: New language features?
« Reply #147 on: September 29, 2024, 04:43:08 pm »
On the matter of how untyped vars in the declaration section I'm not understanding how people think this can work. What happens here? The type changes at runtime does it not? There's no way to solve this that I can see. Maybe I'm missing what people are suggesting though.

Code: Pascal  [Select][+][-]
  1. var
  2.   i;
  3. begin
  4.   if Something then
  5.     i := 1
  6.   else
  7.     i := 'hello';
  8. end.
  9.  

Martin brings up a good point about inferred types. If you have a function and you change the type all the inferred types could change and completely break code. That's a very real problem.

I would guess in 99% of code the loop iterator could be an Integer so that's a reasonable default. If you need a larger range then you can still declare the type. For me a sane default would save me time. For..in loops are pretty easy to reason about to so inferring the type from the collection makes sense to me.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10450
  • Debugger - SynEdit - and more
    • wiki
Re: New language features?
« Reply #148 on: September 29, 2024, 04:53:03 pm »
On the matter of how untyped vars in the declaration section I'm not understanding how people think this can work. What happens here? The type changes at runtime does it not? There's no way to solve this that I can see. Maybe I'm missing what people are suggesting though.

Code: Pascal  [Select][+][-]
  1. var
  2.   i;
  3. begin
  4.   if Something then
  5.     i := 1
  6.   else
  7.     i := 'hello';
  8. end.
  9.  
I answered that a few posts back. In the hypothetical case of such a feature: First assignment does the inferring. So in this example the 2nd cause a compiler error.

But as I also said: I do not wont any type inferring at all. Not this way, not inlined, not at all.

Quote
I would guess in 99% of code the loop iterator could be an Integer so that's a reasonable default. If you need a larger range then you can still declare the type. For me a sane default would save me time. For..in loops are pretty easy to reason about to so inferring the type from the collection makes sense to me.

I just hit Shift - Ctrl - C on the loop var.  And one short look up to confirm. I can probably do that a thousand time and still have spent less time that I would if I had to find one single bug from wrong inferring. But of course such timings are subjective.

That actually gave me a thought. It would be nice if codetool could just show a hint (for a few seconds) whenever it did something. So then the hint would say `Local var "i" created as "integer"` and all would be fine (or it would say, it couldn't create it / it does that in the messages anyway). ... Maybe someday.


gidesa

  • Full Member
  • ***
  • Posts: 125
Re: New language features?
« Reply #149 on: September 29, 2024, 06:32:18 pm »
Hello,
@Ryan J if you want inline variables there is a solution with anonymous procedure:

Code: Pascal  [Select][+][-]
  1. // test inline variables
  2. // require FPC 3.3.1 or higher
  3. program Project1;
  4. {$MODE Delphi}
  5.  
  6. uses
  7.   SysUtils;
  8. var
  9.   x: Integer ;
  10. begin
  11.   x:=12;
  12.  
  13.   TProcedure(procedure
  14.   var
  15.      z: string;
  16.   begin
  17.     z:='Test inline ';
  18.     writeln('Hello! '+z+IntToStr(x));
  19.   end
  20.     )();  // note the "()" to soon execute the procedure
  21.  
  22. //  writeln(z);   // compiler error: identifier not found "z"
  23.   readln;
  24.  
  25. end.  

It's a maybe "modern" form of the subprocedure explained by Warfley e Martin.
But this not compile on FPC 3.2.2. And clearly it's more verbose that C/C++.

 

TinyPortal © 2005-2018