Recent

Author Topic: What is the difference between the new ADVANCED RECORD and the old OBJECT?  (Read 30531 times)

jamie

  • Hero Member
  • *****
  • Posts: 6090
I did not post the 3.2.0 output here. Those are both 3.0.4 64 bit.

What i was saying is the first line of asm that u see there is missing from the 3.2.0 output.

And because of that the inline proxy over to a procedure fails with 3.2.0

No worries , i moved that project to delphi and most likely will do the same for others when 3.0.4 goes dead
The only true wisdom is knowing you know nothing

Blade

  • Full Member
  • ***
  • Posts: 177
First there were record, procedures and functions.
Next, with the emergence of OOP, there were objects.
Next, Borland has introduced classes in which there are additional opportunities. And to force everyone to switch to a new way of declaration, a objects was sign as deprecated.
Later, when it was necessary to merge fields and methods again, it was already inconvenient to revive objects declared as deprecated. And decided to expand records.
And, accordingly, all the new features have tried to do only for records, skipping the implementation for objects, so that they correspond to the previously established policy. And either intentionally made mistakes in the support of old objects, or rather greatly inflated random errors to convince not to use objects.

The classes concept already existed in the Clascal language for Apple's Lisa computer (the earliest object-oriented Pascal dialect), but it was abandoned by Larry Tesler after being advised by Niklaus Wirth. Later, classes were reintroduced by Borland for Delphi.

True, in terms of Class-based OOP.  Clascal was a play on words, "Pascal with classes".  Kind of like the supposedly original name of C++, C with classes.  But, it arguably should not be ignored that many programmers and programming languages have used objects without classes.  Some examples of this are JavaScript and Lua, which are Prototype-based OOP languages, and newer languages like Go (Golang) which avoids Class-based OOP.  In many cases and other languages, people often just want extensible associative arrays and the convenience of dot notation, not the baggage of Class-based OOP.

To add to the fun, there is of course C and structs, but arguments on whether or not that can even be considered OO (or can be implemented like such) can make you dizzy, often the arguments center around if having objects without methods and/or classes is really OOP.  Arguments along those lines can become quite philosophical, where people believe that nouns and verbs (data and functions) should remain separate from each other.  Additionally, you can have objects without classes that are data only, inherit, and/or are encapsulated, but the implementation becomes a sticking point.  There are competing views as to what exactly OO is.

Be that as it may, clearly there is a place for the "plain" object concept or a simpler implementation of OO that is not so class-centric.  I think a reflection of such is why we have this interesting juggling of advanced records, objects, and classes in our language.  I think people have different comfort levels and views about how deep to go or how often they need Class-based OOP.  The great thing about Object Pascal/Free Pascal, is that as a hybrid language, we have options.  Arguably, OOP is done more gently and optionally in our language.
« Last Edit: March 23, 2021, 02:17:00 pm by Blade »

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11383
  • FPC developer.
  In many cases and other languages, people often just want extensible associative arrays and the convenience of dot notation, not the baggage of Class-based OOP.

Dot notation vs -> is a C++ problem. It doesn't have anything to do with class based OOP, since the language can just autoderef, like many languages do (include FPC in some modes)

Quote
Be that as it may, clearly there is a place for the "plain" object concept or a simpler implementation of OO that is not so class-centric. 

The trouble is that for that small class of cases you either have to have a lot of explicit reference usage around it (like in C++ but I assume that could also be done smarter) or have two OO models.

Neither is a very attractive solution. Moreover we already have 5MLoc of class based code.

Blade

  • Full Member
  • ***
  • Posts: 177
Quote
Be that as it may, clearly there is a place for the "plain" object concept or a simpler implementation of OO that is not so class-centric. 
Quote
The trouble is that for that small class of cases you either have to have a lot of explicit reference usage around it (like in C++ but I assume that could also be done smarter) or have two OO models.

Neither is a very attractive solution. Moreover we already have 5MLoc of class based code.

I have a tremendous amount of respect and empathy for developers, and I'm quite sure two OO models is not something that many would find agreeable.  In fact, I'm at peace with how Class-based OOP was implemented in Free Pascal/Delphi. 

But, it doesn't change the fact that other languages took different paths and it's working for them or that many programmers are not comfortable with the conventional implementations of Class-based OOP, having fragmented data merged with methods (the nouns should be separated from verbs debate), or with particular pillars of OOP like inheritance (which also gets into the composition versus inheritance debates) and encapsulation.  Even the creator of Pascal (Niklaus Wirth) appeared to give some push back on the various pillars and use cases of conventional OOP, where they can be different useful alternatives in various situations.  Of course in certain cases, the full glory of Class-based OOP (with inheritance, encapsulation, polymorphism, etc...) is exactly the right answer.  Just not always the answer.

In Object Pascal circles, it appears that records and advanced records are kind of like viable alternatives.  If you look at many of the criticisms of conventional OOP, you can see that records and advances records can also be a way to provide more philosophically agreeable alternatives to many.  That is: you can separate nouns from verbs (unwanted coupling of fragmented data with methods/among multiple classes), choose composition over inheritance (or choose explicit inheritance with records when wanted), rely more on using units as way to extend program functionality, a reduction in Class-based OOP specific syntax/keeping smaller programs simple, easier troubleshooting, etc... As for "old school Objects", arguably they can be seen as a simpler way to deal with OOP or offer some specifically wanted advantage.

I'm of the opinion that the situation works for many Object Pascal programmers, where they can choose suitable options of their liking.  Definitely not saying that such a situation would be viewed as optimal by most developers (who are looking at things at a larger scale than most), but forcing only a certain path might not be helpful to many either.  Perhaps that's partly how Borland/Embarcadero put Delphi into a messy situation with advanced records and their implementations of OOP.  Don't see any easy answers.
« Last Edit: March 25, 2021, 03:00:15 pm by Blade »

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #49 on: October 20, 2021, 05:52:05 pm »
Quote
Feature                                    Record     Object        Class
Class constrcutor and destrutor   Yes           Yes            Yes

The constructor for record is not really constructor. Memory is allocated by the definition of a record variable, and its constructor does only the initializing functions.
Destructors are not allowed for record.

I'm studying advanced records. But I'm reluctant about their usefulness compared to classes especially when I read things like the ones above, concerning their memory management. So, to clarify, I've 2 questions - concerning advanced records - which are:
- if we use a recNew:= TRecord.Create(...);, does recNew point towards an allocated memory inside the stack (I'm worried about stackoverflow), or inside the heap like objects?
- as there are no destructors for such advanced records, are they deallocated automatically? Whether they are in the stack or in the heap?
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #50 on: October 20, 2021, 08:22:28 pm »
Records are (unlike classes) not pointers, but describe actual memory blocks.

When using a record you need to make sure where it is allocated. If it's a global variable, it is located at the DATA segment, if it's a local variable it's on the stack. You can also place it on the heap but then you need to access it via pointers.
The memory of the stack and global variables is always freed (stack when the function returns, global variables when the program is killed).

Classes are always located on the heap and thereby behave like records if you use pointers. Basically class types are pointers in disguise. They can only be placed on the heap which makes them require manual memory management.

This has some implications. When assigning a class type, you just copying the pointer, it is just a reference to the same memory location:
Code: Pascal  [Select][+][-]
  1. c1, c2: TMyClass;
  2. begin
  3.   c1 := TMyClass.Create;
  4.   c2 := c1;
  5.   c2.A := 'foo';
  6.   WriteLn(C1.A); // is Foo
  7.   c2.Free; // same as c1 therefore c1 is freed
With records you are actually copying the data:
Code: Pascal  [Select][+][-]
  1. r1, r2: TMyRecord;
  2. begin
  3.   r1 := TMyRecord.Create('Foo'); // initialze A with 'Foo'
  4.   r2 := r1;
  5.   r2.A := 'Bar';
  6.   WriteLn(r1.A); // Shows 'Foo' because r2 is a copy
But with the use of Pointers this is still rather similar:
Code: Pascal  [Select][+][-]
  1. type PMyRecord = ^TMyRecord;
  2. var r1, r2: PMyRecord;
  3. begin
  4.   New(r1, 'Foo'); // calls create with argument 'Foo'
  5.   r2 := r1;
  6.   r2^.A := 'Bar';
  7.   WriteLn(r1^.A); // Prints 'Bar' as both reference the same record
  8.   Dispose(r2); // Same memory as r1 therefore both are freed

So I think it is better to not think of records and classes as fundamentally different with respect to memory management, but classes are just pointers to datastructures not so disimilar to records, but they are just hiding it.

But, the Destructor does not only do memory management, but also calls the destroy function which can contain arbitrary code. To simulate that you can simply add a destroy function to the record, basically the same effect.

Note, you can also emulate class like syntax with some modeswitches:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$H+}
  2. {$ModeSwitch advancedrecords}
  3. {$ModeSwitch autoderef}
  4.  
  5. type
  6.   PMyRec = ^TMyRec;
  7.  
  8.   TMyRec = record
  9.   private
  10.     FA: String;
  11.   public
  12.     class function Create(const AValue: String): PMyRec; static;
  13.     procedure Destroy;
  14.     procedure Free;
  15.  
  16.     property A: String read FA write FA;
  17.   end;
  18.  
  19. class function TMyRec.Create(const AValue: String): PMyRec;
  20. var
  21.   Self: PMyRec absolute Result;
  22. begin
  23.   New(Self);
  24.   Self.A := AValue;
  25. end;
  26.  
  27. procedure TMyRec.Destroy;
  28. begin
  29.   Dispose(PMyRec(@Self));
  30. end;
  31.  
  32. procedure TMyRec.Free;
  33. begin
  34.   if Assigned(@Self) then
  35.     Destroy;
  36. end;
  37.  
  38. var
  39.   r1, r2: PMyRec;
  40. begin
  41.   r1 := TMyRec.Create('Foo');
  42.   r2 := r1;
  43.   r2.A := 'Bar';
  44.   WriteLn(r1.A); // Prints Bar because this is just a pointer
  45.   r2.Free;
  46. end.
It's not perfect because you can't add the class method Create to PMyRec (which is how class constructors work) so you have to use TMyRec for creation but PMyRec variable type, but aside from that, this is pretty much all what classes do.
From a syntactic level classes are just advanced records or objects with {$ModeSwitch autoderef}  activated.

Thats why I personally think that introducing classes was a terrible idea by Borland. With respect to memory management, they are just like objects but worse as you can simply do less with them.
If you ask me, the best solution would be to just throw classes away (or deprecate them) and give all their functionality to advanced records, making both classes and objects only be there fore legacy code compatibility and having with the new advanced records a datatype model which is simply more powerful than classes thanks to more options with respect to memory managment.

And the features of autoderef could be incorporated right into the syntax:
Code: Pascal  [Select][+][-]
  1. type
  2.   PMyRec = autoderef ^TMyRec;
  3.   TMyRec = record
  4.     ...
  5.   end;
  6.  
  7. var
  8.   r: PMyRec;
  9. begin
  10.   r := PMyRec := PMyRec.Create('Foo'); // autoderef also applies to class functions
  11.   r.A := 'Bar';
  12.   r.Free;
  13. end;
There wouldn't even be much difference to current classes on a syntactical level (if typing a few ^ is to hard for people)
« Last Edit: October 20, 2021, 08:31:45 pm by Warfley »

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #51 on: October 21, 2021, 09:05:46 am »
- if we use a recNew:= TRecord.Create(...);, does recNew point towards an allocated memory inside the stack (I'm worried about stackoverflow), or inside the heap like objects?

Unlike for classes a constructor of a record is not also an allocator. Essentially it's a function in the form of class function Create(...): TRecord; static; where the return value is accessed with Self instead of Result. Nothing else special is done there.

- as there are no destructors for such advanced records, are they deallocated automatically? Whether they are in the stack or in the heap?

Advanced records are just like ordinary records with the added benefit that you can add visibility sections, methods, properties and operators to them. Otherwise they behave exactly the same.

The ability to add operators is the main advantage of advanced records: these operators can be picked up by generics, while global operator declarations can only be picked up if they were available during the time the generic itself was declared and not when it is specialized.

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #52 on: October 21, 2021, 10:11:09 am »
• Thanks to @Warfley (nb: for me, an object is a class instance; I may not have posted my question in the right thread, talking from "old OBJECT") and thanks to @PascalDragon for their answers.

I've found an example from @ASerge...:

Code: Pascal  [Select][+][-]
  1.     {$APPTYPE CONSOLE}
  2.     {$IFDEF FPC}
  3.       {$MODE OBJFPC}
  4.       {$MODESWITCH ADVANCEDRECORDS}
  5.     {$ENDIF}
  6.      
  7.     type
  8.       PMyType = ^TMyType;
  9.       TMyType = record
  10.         constructor Init(AValue: Integer);
  11.       end;
  12.      
  13.     constructor TMyType.Init(AValue: Integer);
  14.     begin
  15.       Writeln('Init ', AValue);
  16.     end;
  17.      
  18.     var
  19.       P: PMyType;
  20.     begin
  21.       New(P, Init(123)); // or P := New(PMyType, Init(123));
  22.       Dispose(P);
  23.       Readln;
  24.     end.
  25.  

...which illustrates the response from @PascalDragon i.e. they are indeed records like the others records from the memory management point of view: we always have to call New and Dispose, if we want to allocate them inside the heap. I had a doubt because of the constructor keyword. Btw, @Serge writes constructor Init and not constructor Create in the records. I think it's probably to disconfuse between "constructors", whether it is an object instance (Create, i.e. located in the heap) or an advanced record variable (Init, i.e. located in the stack). And I've well understood the advanced record's advantages with their class operators (like =, <, >).


• Having understood  the memory management of the advanced records (same way of managing the memory as the "old" ones), leads me to the following question: if I want to make a TAdvancedRecList = specialize TFPGList< TAvancedRecord > containing advanced records allocated in the heap (so, using New(pAdvRecFoo);), do I have to declare...:

Code: Pascal  [Select][+][-]
  1. {$MODESWITCH ADVANCEDRECORDS}
  2.  
  3. uses
  4.   sysutils, fgl;
  5.  
  6. type
  7.   TAdvRecord = record
  8.     field1: string;
  9.     field2: integer;
  10.     class operator = (r1, r2: TRecord): Boolean;
  11.     constructor Create(f1: string; f2: integer);
  12.   end;
  13.  
  14.   TAdvRecList = specialize TFPGList<TAdvRecord>;

...or do I have to declare...


Code: Pascal  [Select][+][-]
  1. {$MODESWITCH ADVANCEDRECORDS}
  2.  
  3. uses
  4.   sysutils, fgl;
  5.  
  6. type
  7.   PAdvRecord = ^TAdvRecord;
  8.   TAdvRecord = record
  9.     field1: string;
  10.     field2: integer;
  11.     class operator = (r1, r2: TRecord): Boolean;
  12.     constructor Create(f1: string; f2: integer);
  13.   end;
  14.  
  15.   TAdvRecList = specialize TFPGList<PAdvRecord>;

... in order to store the advanced record's pointers (previously allocated in the heap with  New(pAdvRecFoo);)?

This question, this doubt comes from this _post_, where the Pointer type on its advanced record Type is not used. So, I guess that this example uses a specialized list which is created in the same cloture \ namespace \ calls stack, as the one (calls stack) of the unallocated records it is storing.



« Last Edit: October 21, 2021, 10:26:55 am by devEric69 »
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #53 on: October 21, 2021, 03:08:58 pm »
I found the answer in this _post_:

Quote from: Thaddy
>
> Does anyone know if TFPGList will work with records (I know it works
> with simple datatypes like integer and string)?
>

You will have to use Pointer to record and allocate them yourself, it seems, with  p:=New(TAdvRecord) or something.
During my experiments I ran into that. Should be solvable.

So, as far as I understand, nothing magic in memory allocation, including with this advanced records and their "constructor Init" (that said, it - the compiler - still does a lot of work for us in the background to automatically manage a specialized "type safe" container-list-map-..., even with these advanced records).
« Last Edit: October 21, 2021, 03:36:32 pm by devEric69 »
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #54 on: October 21, 2021, 07:55:25 pm »
The thing with records is, you have complete control when and where to place them. So that you need to allocate memory and use pointers is not quite correct. Of course TFPGMap will internally do the memory allocation, but generally speaking you can use records on any memory

For example, you can easiely make a List that is on the stack with records:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. type
  7.  
  8.   { TStackList }
  9.  
  10.   generic TStackList<T, const MaxCount: SizeInt> = record
  11.   private type PT = ^T;
  12.   private const Capacity = MaxCount * SizeOf(T);
  13.   private
  14.     FData: Array[0..Capacity - 1] of Byte;
  15.     FLength: SizeInt;
  16.  
  17.     function GetItem(AIndex: SizeInt): T;
  18.     procedure SetItem(AIndex: SizeInt; const AValue: T);
  19.   public
  20.     constructor Create;
  21.     procedure Destroy;
  22.  
  23.     function Add(constref AValue: T): SizeInt;
  24.     procedure Delete(AIndex: SizeInt);
  25.  
  26.     procedure Clear;
  27.  
  28.     property Items[AIndex: SizeInt]: T read GetItem write SetItem; default;
  29.     property Length: SizeInt read FLength;
  30.   end;
  31.  
  32.  
  33. constructor TStackList.Create;
  34. begin
  35.   FLength := 0;
  36. end;
  37.  
  38. procedure TStackList.Destroy;
  39. begin
  40.   Clear;
  41. end;
  42.  
  43. function TStackList.Add(constref AValue: T): SizeInt;
  44. begin
  45.   if FLength >= MaxCount then Exit(-1);
  46.   Result := FLength;
  47.   Initialize(PT(@FData)[Result]);
  48.   Items[Result] := AValue;
  49.   Inc(FLength);
  50. end;
  51.  
  52. procedure TStackList.Delete(AIndex: SizeInt);
  53. begin
  54.   if AIndex >= FLength then Exit;
  55.   Finalize(PT(@FData)[AIndex]);
  56.   Dec(FLength);
  57.   Move(PT(@FData)[AIndex + 1], PT(@FData)[AIndex], FLength - AIndex);
  58. end;
  59.  
  60. procedure TStackList.Clear;
  61. begin
  62.   while FLength > 0 do
  63.     Delete(FLength - 1);
  64. end;
  65.  
  66. type
  67.  
  68.   TTestRec = record
  69.   A, B: String;
  70.   constructor Create(const AA: String; const AB: String);
  71.   end;
  72.  
  73. constructor TTestRec.Create(const AA: String; const AB: String);
  74. begin
  75.   A := AA;
  76.   B := AB;
  77. end;
  78.  
  79. type
  80.   TTestList = specialize TStackList<TTestRec, 1024>;
  81.  
  82. var
  83.   Lst: TTestList;
  84.   i: Integer;
  85. begin
  86.   Lst := TTestList.Create;
  87.   try
  88.     Lst.Add(TTestRec.Create('Foo', 'Bar'));
  89.     Lst.Add(TTestRec.Create('FooBar', 'BarFoo'));
  90.     for i := 0 to Lst.Length - 1 do
  91.       WriteLn('Entry: ', i, 'A: ', Lst[i].A, ', B:', Lst[i].B);
  92.   finally
  93.     Lst.Destroy;
  94.   end;
  95. end.

Here, because it is located on the stack (or to be more precise, a global variable), we don't need to do *any* memory allocation. That said, we still need to perform initialization and cleanup (in Add and Delete).

If we had alloca, it would even be possible to have this list dynamically growing.

To sum it up, you should think about allocation and initialization as different things. It doesn't matter how the memory for your record was allocated as long as you initialize and finalize properly your code can be completely independent from allocation
« Last Edit: October 21, 2021, 08:12:10 pm by Warfley »

devEric69

  • Hero Member
  • *****
  • Posts: 648
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #55 on: October 21, 2021, 08:07:54 pm »
Wow! Thank you, for this programming lesson.
use: Linux 64 bits (Ubuntu 20.04 LTS).
Lazarus version: 2.0.4 (svn revision: 62502M) compiled with fpc 3.0.4 - fpDebug \ Dwarf3.

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #56 on: October 22, 2021, 12:46:50 pm »
A class / object / advanced record is basically a block of memory with an index to show where the different parts reside. Methods are just functions / procedures that have an invisible @self pointer as first parameter. So, nothing is stopping you to allocate simple records on the stack and access them through pointers. In the past, before classes existed, I put all of the parts in linked lists, so it was easy to inherit and with an optional index to access the larger ones faster. Which is basically what a modern class is, although the compiler magic hides the pointers and packs them into arrays.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: What is the difference between the new ADVANCED RECORD and the old OBJECT?
« Reply #57 on: October 22, 2021, 03:51:43 pm »
Btw, @Serge writes constructor Init and not constructor Create in the records. I think it's probably to disconfuse between "constructors", whether it is an object instance (Create, i.e. located in the heap) or an advanced record variable (Init, i.e. located in the stack).

You can name the constructor method any way you want, even for classes. The only exception is if you want to override an existing constructor then you need to use the same name, but otherwise you're free to use Init in a class as well.

So, as far as I understand, nothing magic in memory allocation, including with this advanced records and their "constructor Init" (that said, it - the compiler - still does a lot of work for us in the background to automatically manage a specialized "type safe" container-list-map-..., even with these advanced records).

That management indeed has nothing to do with advanced records or not, it's simply related to whether you use e.g. AnsiString, Variant or IInterface.

 

TinyPortal © 2005-2018