Recent

Author Topic: friend members  (Read 2208 times)

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
friend members
« on: June 26, 2023, 04:39:21 pm »
Hello,
some other languages offer to classes definitions a visibility option called "friend". Saying that in Free Pascal terms, friend members of one class would therefore be accessible to all classes in the same namespace (even if Free Pascal does not have a namespace keyword, but this functionality is achieved using the dotted units), even if classes are defined into different units.

Example:
Code: Pascal  [Select][+][-]
  1. unit Namespace1.Class1;
  2.  
  3. type
  4.   TClass1 = class(TObject)
  5.     friend // if it was present in the language
  6.     Member1: Integer;
  7.   end;
  8.  
  9. unit Namespace1.Class2;
  10.  
  11.   TClass2 = class(TObject)
  12.     // Class1.Member1 is accessible because both classes are defined in the same namespace
  13.   end;
  14.  

In Free Pascal to obtain this it seems that I need to declare the class1.member1 as protected, but I also need to define the class (say class2) that must access to class1.member1 in the same unit of class1.

Code: Pascal  [Select][+][-]
  1. unit TheUnit;
  2.  
  3. type
  4.  
  5.   TClass1 = class(TObject)
  6.     protected
  7.     Member1: Integer;
  8.   end;
  9.  
  10.   TClass2 = class(TObject)
  11.     // Class1.Member1 is accessible because protected and we are in same unit
  12.   end;
  13.  

Is this correct, or is there some other possibility?
« Last Edit: June 26, 2023, 04:41:43 pm by Чебурашка »
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #1 on: June 26, 2023, 04:46:41 pm »
Is this correct, or is there some other possibility?

Of course the question is "Is there some other possibility, not counting to put the two classes into separate units, and marking Class1.Member1 as public", as this is obvious in any language.
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12857
  • FPC developer.
Re: friend members
« Reply #2 on: June 26, 2023, 04:47:11 pm »
The namespace unit of Pascal is the unit.  The prefix has only meaning in finding and importing files, not in language elements.   So  symbols in unit a.b.c and unit a.b.a are not closer related than any other units.

This is where the analogy with C#/Java doesn't go up, and is quite fundamental. Pascal finds units differently and doesn't store its unit in hierarchical dirs or registries, and doesn't have multi stage compilation processes to byte code.

Classes in the same unit can already access each other's symbols, when visibility modifiers that are not "strict" are used.

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #3 on: June 26, 2023, 04:49:06 pm »
The namespace unit of Pascal is the unit.  The prefix has only meaning in finding and importing files, not in language elements.   So  symbols in unit a.b.c and unit a.b.a are not closer related than any other units.

This is where the analogy with C#/Java doesn't go up, and is quite fundamental. Pascal finds units differently and doesn't store its unit in hierarchical dirs or registries, and doesn't have multi stage compilation processes to byte code.

Classes in the same unit can already access each other's symbols, when visibility modifiers that are not "strict" are used.

Ok thanks marcov, I understand that the answer is no, the must live in the same unit.
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

Warfley

  • Hero Member
  • *****
  • Posts: 2059
Re: friend members
« Reply #4 on: June 26, 2023, 04:51:48 pm »
Pascal doesn't let friends touch each others private parts. But Private and Protected are "bugged" in the sense that they are actually visible in the whole unit:
Code: Pascal  [Select][+][-]
  1. unit MyUnit;
  2. ...
  3. type
  4.   TClass1 = class
  5.   private FFoo: Integer;
  6.   end;
  7.  
  8.   TClass2 = class
  9.   private procedure SetFoo(cls1: TClass1);
  10.   end;
  11.  
  12. ...
  13.  
  14. procedure TClass2.SetFoo(cls1: TClass1);
  15. begin
  16.   cls1.FFoo := 42; // Access private member possible because same unit
  17. end;

While I call this "bugged", this is (now) intended behavior (one of those bugs that because everyone used it became a feature). This is why today there are strictprivate and strictprotected which serve the function that private and protected were originally supposed to serve (i.e. something that is only visible inside the class/in inheritance and not in the whole unit).

PS: Friends are not just for namespaces, in C++ for example you can explicetly make other types or functions friends:
Code: C  [Select][+][-]
  1. class MyClass1 {
  2. private:
  3.   int Foo;
  4. friend class MyClass2;
  5. friend std::ostream& operator<<(std::ostream& out, const MyClass1& o);
  6. };
  7. // Different file
  8. class MyClass2 {
  9.   void SetFoo(MyClass1 const &cls1) { cls1.Foo = 42; } // Access to private field
  10. }
  11.  
  12.  
  13. std::ostream& operator<<(std::ostream& out, const MyClass1& o) {
  14.   reutrn out << o.Foo; // Access to private field
  15. }
« Last Edit: June 26, 2023, 04:53:49 pm by Warfley »

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #5 on: June 26, 2023, 05:10:08 pm »
Just to understand why I was asking this.

In my opinion, putting multiple classes definitions in one unit is very senseful when the are correlated, so that at usage stage one just need to include one single unit.
On the other hand this might result in very large units. Someone might not find this a problem, but I don't like so much (just taste I think).

Having the possibility of putting them into separate units helps to keep units smaller.

If this is not possible I believe that I will use the includes technique:

Code: Pascal  [Select][+][-]
  1. unit TheUnit
  2.  
  3. interface
  4.  
  5. {$I 'unit1_interface.inc'}
  6. {$I 'unit2_interface.inc'}
  7. // ..
  8.  
  9. implementation
  10.  
  11. {$I 'unit1_implementation.inc'}
  12. {$I 'unit2_implementation.inc'}
  13.  
  14. end.
  15.  

Especially because Holy Lazarus is so smart that CTRL+SHIFT+UP/DOWN moves smoothly betweeen the two correlated include files.
« Last Edit: June 26, 2023, 05:24:06 pm by Чебурашка »
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12857
  • FPC developer.
Re: friend members
« Reply #6 on: June 26, 2023, 05:11:31 pm »
While I call this "bugged", this is (now) intended behavior (one of those bugs that because everyone used it became a feature).

It is not known how intended the "bugged" behaviour was. It is notable that people only started complaining about it around 1999-2000 when C# and Java became popular.

Quote
PS: Friends are not just for namespaces, in C++ for example you can explicetly make other types or functions friends:

Somewhat interesting, but particularly how C++ handles that between compilation units then, and how much of it is declare before use?



Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #7 on: June 26, 2023, 05:30:24 pm »
It is notable that people only started complaining about it around 1999-2000 when C# and Java became popular.

I also found this feature useful when working with the languages you cited (class files did not grow too much).
Also with C# it is even possible to split a class definition over multiple source files (maybe also java has this feature I don't know).
Just trying to organize the code (more or less) one class per file now that I work with FPC.
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12857
  • FPC developer.
Re: friend members
« Reply #8 on: June 26, 2023, 05:42:22 pm »
I also found this feature useful when working with the languages you cited (class files did not grow too much).
Also with C# it is even possible to split a class definition over multiple source files (maybe also java has this feature I don't know).
Just trying to organize the code (more or less) one class per file now that I work with FPC.

As said the namespace system (higher than units, i.e. dotted unitnames) of Pascal is rudimentary, and this is near impossible.

I'd caution to go overboard with splitting up units, as that goes counter to the design principles. If you want that way, you might be more interested in derivatives that are essentially C# with some pascal syntax like PascalABC.NET and Remobjects Oxygene.

Those more often do adher to Java/C# namespace principles. Classic Pascal/Delphi does not.

Warfley

  • Hero Member
  • *****
  • Posts: 2059
Re: friend members
« Reply #9 on: June 26, 2023, 09:21:03 pm »
It is not known how intended the "bugged" behaviour was. It is notable that people only started complaining about it around 1999-2000 when C# and Java became popular.
Yeah, people start complaining about mis-designs in OOP when OOP started to become popular. Before Java OOP was just a tag on. The popular "OOP" languages in the 80s where things like C++ or TP, which where procedural languages with classes (C++ was originally named "C with Classes"). It was just another syntactic tool in the procedural programming world. Java changed everything, with Java OOP became a whole philosophy, and the main message is "Encapsulation".
And this is what triggered this debate. If OOP is not just a tool for procedural programming but a philosophy centered around encapsulation, then the main form of encapsulation not being able to enforce encapsulation is a big deal.

Note that it is also the age old discussion if in C++ a class instance should be able to access private members of another class instance. Because in Ruby for example does not allow this. It is arguable that the ruby way of doing it is more in line with the ideals behind OOP, and today it is often argued that this is for allowing to implement highly efficient copy constructors and assignment operators, but as there is no evidence of this being an active concious decision, most probably I would say is that it was just never though about that there may be a difference between class private and instance private until Ruby came along and showed it's interpretation of the encapsulation principle.

So this discussion is not just for Delphis private since Java and C#, C++ has a similar discussion since Ruby came out

Warfley

  • Hero Member
  • *****
  • Posts: 2059
Re: friend members
« Reply #10 on: June 26, 2023, 09:46:31 pm »
In my opinion, putting multiple classes definitions in one unit is very senseful when the are correlated, so that at usage stage one just need to include one single unit.
On the other hand this might result in very large units. Someone might not find this a problem, but I don't like so much (just taste I think).
Without knowing your code, this usually is a sign that you have either to many interdependent classes, or your classes are getting way to big. If it's the former, than maybe you should consider making some of the private fields you need to access public, or put them in a common base class and make them protected, or make a distinction between a working unit and a publishing unit, where the working unit has accessors to those mebers, but this is only used within the classes that need it:
Unit that will be included to use TMyClass externally:
Code: Pascal  [Select][+][-]
  1. unit MyUnit;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   MyUnit.Internal;
  9.  
  10. type
  11.   TMyClass = MyUnit.Internal.TMyClass; // Publish TMyClass
  12.  
  13. implementation
  14.  
  15. end.
  16.  
Unit that defines TMyClass internally:
Code: Pascal  [Select][+][-]
  1. unit MyUnit.Internal;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils;
  9.  
  10. type
  11.  
  12.   { TMyClass }
  13.  
  14.   TMyClass = class
  15.   private
  16.     FFoo: Integer;
  17.   public
  18.     procedure PrintFoo;
  19.   end;
  20.  
  21.   { TMyClassPublisher }
  22.  
  23.   TMyClassPublisher = class helper for TMyClass
  24.   private
  25.     function GetFoo: Integer;
  26.     procedure SetFoo(AValue: Integer);
  27.   public
  28.     property Foo: Integer read GetFoo write SetFoo;
  29.   end;
  30.  
  31. implementation
  32.  
  33. { TMyClass }
  34.  
  35. procedure TMyClass.PrintFoo;
  36. begin
  37.   WriteLn(FFoo);
  38. end;
  39.  
  40. { TMyClassPublisher }
  41.  
  42. function TMyClassPublisher.GetFoo: Integer;
  43. begin
  44.   Result := Self.FFoo;
  45. end;
  46.  
  47. procedure TMyClassPublisher.SetFoo(AValue: Integer);
  48. begin
  49.   Self.FFoo := AValue;
  50. end;
  51.  
  52. end.
  53.  
Using the internal unit:
Code: Pascal  [Select][+][-]
  1. unit FactoryUnit;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   MyUnit.Internal;
  9.  
  10.  
  11. function CreateClass(Foo: Integer): TMyClass;
  12. implementation
  13.  
  14. function CreateClass(Foo: Integer): TMyClass;
  15. begin
  16.   Result := TMyClass.Create;
  17.   Result.Foo := Foo; // Access to the member that is only published in MyUnit.Internal
  18. end;
  19.  
  20. end.
  21.  
Using the published unit:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   {$IFDEF UNIX}
  7.   cthreads,
  8.   {$ENDIF}
  9.   Classes, MyUnit, FactoryUnit;
  10.  
  11. var
  12.   c: TMyClass;
  13. begin
  14.   c := CreateClass(42);
  15.   C.PrintFoo;
  16.   c.Foo := 32; // Doesn't work because type helper is not published by MyUnit
  17.   ReadLn;
  18. end.
  19.  

Splitting up between internal and external code is somethign I do quite often. Often it's not necessarily with a type helper to accessing private fields, because I rarely have the situation that I need to access another classes private fields, but this is usually for helper functions that the outside don't need to see (e.g. different functions for Windows and Unix to ensure cross compatibility).

If it's the latter and you have few classes which get really big, then this is usually a sign that you do to much stuff within one class. It is often helpful to put code in helper functions which are not directly members of the class, but just plain old functions, or to split up big classes in multiple classes.

That said, there is one big problem and this is if you need cyclic references, then you must put them all in one class. And I also have some projects where I have units with like 1000-1500 lines of code because of this. But thats the exception and I think this can still be managable thanks to IDE support

PascalDragon

  • Hero Member
  • *****
  • Posts: 6395
  • Compiler Developer
Re: friend members
« Reply #11 on: June 26, 2023, 11:10:20 pm »
While I call this "bugged", this is (now) intended behavior (one of those bugs that because everyone used it became a feature).

It is not known how intended the "bugged" behaviour was.

The Turbo Pascal 6.0 Programmer's Guide writes the following in Object types -> Components and scope about the usage of private inside object-types (which are after all the spiritual predecessor of Delphi-style classes):

Quote
The scope of a component identifier declared in the private
section of an object type declaration is restricted to the module
(program or unit) that contains the object type declaration. In
other words, private component identifiers act like normal public
component identifiers within the module that contains the object
type declaration, but outside the module, any private component
identifiers are unknown and inaccessible. By placing related
object types in the same module, these object types can gain
access to each others private components without making the
private components known to other's modules.

So this definitely sounds like the intended behavior to allow for close coupling between objects if desired.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12857
  • FPC developer.
Re: friend members
« Reply #12 on: June 26, 2023, 11:18:03 pm »
It was just another syntactic tool in the procedural programming world. Java changed everything, with Java OOP became a whole philosophy, and the main message is "Encapsulation".

And this is what triggered this debate. If OOP is not just a tool for procedural programming but a philosophy centered around encapsulation, then the main form of encapsulation not being able to enforce encapsulation is a big deal.

There is a difference between "Encapsulation" (or OOP) as a design philosophy, and driving this too the limit in languages, needing new concepts just for exceptions.  Java drove it far, and yes it was like opium for people that never knew anything else.
« Last Edit: June 27, 2023, 09:36:01 am by marcov »

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #13 on: June 27, 2023, 08:52:29 am »
Without knowing your code, this usually is a sign that you have either to many interdependent classes, or your classes are getting way to big.

For me, most commonly this problem emerges when mapping the objects to reflect a database structure (in the so called "Vietnam of programming" context, this is how someone calls the ORMing). In a chain of 4 dependent tables I create a class for each table and then map this dependency that will reflect as 4 dependent classes (not interdependent). There are also other possible approaches but this is the most natural, simple, intuitive etc etc etc.
In some cases it is comportable to create a "Manager" class puts all togherer and incorporates some extra logic to have the db object to be worked out correctly (referenced keys, caches, ...).
The problem is that this Manager class needs to interact the the properties of the db classes, and here is the problem that classes in other units are less accessible (or must be made more accessible by publishing certain things that normally could me left local, also these things would be published only for the Manager's usage, not for other clients).

To be honest, I do not see a limit in pascal design because it forces to relate classes split into different units in a certain way. Is just that allowing the cited "friend" accessibility would allow me to make things quicker.

It's ok, still in love with FPC.
« Last Edit: June 27, 2023, 09:08:48 am by Чебурашка »
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

Чебурашка

  • Hero Member
  • *****
  • Posts: 593
  • СЛАВА УКРАЇНІ! / Slava Ukraïni!
Re: friend members
« Reply #14 on: June 27, 2023, 09:01:42 am »
I also found this feature useful when working with the languages you cited (class files did not grow too much).
Also with C# it is even possible to split a class definition over multiple source files (maybe also java has this feature I don't know).
Just trying to organize the code (more or less) one class per file now that I work with FPC.

As said the namespace system (higher than units, i.e. dotted unitnames) of Pascal is rudimentary, and this is near impossible.

I'd caution to go overboard with splitting up units, as that goes counter to the design principles. If you want that way, you might be more interested in derivatives that are essentially C# with some pascal syntax like PascalABC.NET and Remobjects Oxygene.

Those more often do adher to Java/C# namespace principles. Classic Pascal/Delphi does not.

Thanks for the suggestions, I am very fine with FPC, no need to move to small branches/niches. I will adapt myself/find reasonable solutions, like always.

« Last Edit: June 27, 2023, 09:18:08 am by Чебурашка »
FPC 3.2.0/Lazarus 2.0.10+dfsg-4+b2 on Debian 11.5
FPC 3.2.2/Lazarus 2.2.0 on Windows 10 Pro 21H2

 

TinyPortal © 2005-2018