Lazarus

Free Pascal => General => Topic started by: MarkMLl on May 01, 2019, 06:44:26 pm

Title: Class helpers
Post by: MarkMLl on May 01, 2019, 06:44:26 pm
Two questions. The first: is there a restriction that a class can only be extended by a single helper, after which it's the helper that has to be extended? i.e.

A= class
B= class helper for A
C= class helper for A

is wrong, while

A= class
B= class helper for A
C= class helper(B) for A

is right? Alternatively, is there a rule that only one helper can be declared in a unit?

The problem that I was having was that when compiling using the Lazarus IDE it would fail at the first point it encountered a method from helper B, rather than getting into the unit B and saying "You can't declare C in here".

The second: Can the definition of a class list the names of the units in which it may be extended by a helper?

MarkMLl


Title: Re: Class helpers
Post by: ASBzone on May 01, 2019, 07:26:45 pm
Two questions. The first: is there a restriction that a class can only be extended by a single helper, after which it's the helper that has to be extended? i.e.
...

You may receive better assistance with an actual code block than with a theoretical question.
Title: Re: Class helpers
Post by: furious programming on May 01, 2019, 08:09:42 pm
See the example:

Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}{$LONGSTRINGS ON}{$MODESWITCH TYPEHELPERS}
  2.  
  3. type
  4.   TIntegerHelper = type helper for Integer
  5.   public
  6.     procedure Foo();
  7.   end;
  8.  
  9.   procedure TIntegerHelper.Foo();
  10.   begin
  11.     WriteLn('Integer.Foo');
  12.   end;
  13.  
  14. type
  15.   TAnotherIntegerHelper = type helper for Integer
  16.   public
  17.     procedure Bar();
  18.   end;
  19.  
  20.   procedure TAnotherIntegerHelper.Bar();
  21.   begin
  22.     WriteLn('Integer.Bar');
  23.   end;
  24.  
  25. var
  26.   Value: Integer = 0;
  27. begin
  28.   Value.Foo();
  29.   Value.Bar();
  30. end.

This code does not compile — Foo method is not known for the compiler, because TIntegerHelper is covered by the TAnotherIntegerHelper, so the selected line is invalid. In result, whole content of the TIntegerHelper is not accessible.

So if you want to completely cover the existing helper, don't use inheritance. But if you want to use methods from both helpers, use the inheritance:

Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}{$LONGSTRINGS ON}{$MODESWITCH TYPEHELPERS}
  2.  
  3. type
  4.   TIntegerHelper = type helper for Integer
  5.   public
  6.     procedure Foo();
  7.   end;
  8.  
  9.   procedure TIntegerHelper.Foo();
  10.   begin
  11.     WriteLn('Integer.Foo');
  12.   end;
  13.  
  14. type
  15.   TAnotherIntegerHelper = type helper(TIntegerHelper) for Integer
  16.   public
  17.     procedure Bar();
  18.   end;
  19.  
  20.   procedure TAnotherIntegerHelper.Bar();
  21.   begin
  22.     WriteLn('Integer.Bar');
  23.   end;
  24.  
  25. var
  26.   Value: Integer = 0;
  27. begin
  28.   Value.Foo();
  29.   Value.Bar();
  30. end.
Title: Re: Class helpers
Post by: MarkMLl on May 01, 2019, 08:24:11 pm
So if you want to completely cover the existing helper, don't use inheritance. But if you want to use methods from both helpers, use the inheritance:

Thanks for that, so in effect helpers are a linked list with no provision for branches.

Looking back through the record I can see that when class sealing was introduced I asked on the ML whether a sealed class could be extended by a helper. Nobody had a confident answer to that, but I think it fair to also ask whether a chain of class helpers can be sealed to prevent anybody extending it further.

MarkMLl
Title: Re: Class helpers
Post by: furious programming on May 01, 2019, 10:26:24 pm
Looking back through the record I can see that when class sealing was introduced I asked on the ML whether a sealed class could be extended by a helper.

Of course, sealed classes can be extended by helpers. Example:

Code: Pascal  [Select][+][-]
  1. {$MODE OBJFPC}{$LONGSTRINGS ON}{$MODEsWITCH TYPEHELPERS}
  2.  
  3. type
  4.   TThing = class sealed(TObject)
  5.   public
  6.     procedure Foo();
  7.   end;
  8.  
  9.   procedure TThing.Foo();
  10.   begin
  11.     WriteLn('TThing.Foo');
  12.   end;
  13.  
  14. type
  15.   TThingHelper = class helper for TThing
  16.   public
  17.     procedure Bar();
  18.   end;
  19.  
  20.   procedure TThingHelper.Bar();
  21.   begin
  22.     WriteLn('TThing.Bar');
  23.   end;
  24.  
  25. type
  26.   TThingAnotherHelper = class helper(TThingHelper) for TThing
  27.   public
  28.     procedure Baz();
  29.   end;
  30.  
  31.   procedure TThingAnotherHelper.Baz();
  32.   begin
  33.     WriteLn('TThing.Baz');
  34.   end;
  35.  
  36. var
  37.   Thing: TThing;
  38. begin
  39.   Thing := TThing.Create();
  40.   try
  41.     Thing.Foo();
  42.     Thing.Bar();
  43.     Thing.Baz();
  44.   finally
  45.     Thing.Free();
  46.   end;
  47. end.

Quote
Nobody had a confident answer to that, but I think it fair to also ask whether a chain of class helpers can be sealed to prevent anybody extending it further.

Sealing of classes are used only to block inheritance when creating a new data type (new class). Helpers are used to extend classes (and any other types of data), where inheritance does not occur at all, so the seal in this case does not matter.

Interestingly, it is not possible to seal the helper. Thus, each type can be extended with helpers, and their number is not limited. It is therefore possible to create a theoretically freely long chain of helper inheritance and extend the given type of data without end.
Title: Re: Class helpers
Post by: MarkMLl on May 01, 2019, 10:44:03 pm
Of course, sealed classes can be extended by helpers. Example:
...
Sealing of classes are used only to block inheritance when creating a new data type (new class). Helpers are used to extend classes (and any other types of data), where inheritance does not occur at all, so the seal in this case does not matter.

Interestingly, it is not possible to seal the helper. Thus, each type can be extended with helpers, and their number is not limited. It is therefore possible to create a theoretically freely long chain of helper inheritance and extend the given type of data without end.

Thanks for that. I did say nobody had, and this was a few years ago :-)

I tend to look at things from the point of view of an OS writer, but I'd have thought that being able to forcibly terminate a helper chain would be useful. Or alternatively the second part of my original question, which was whether helpers could be rejected if they weren't in a list of specified units- which is probably infeasible, since I can't think of anywhere that a list of units is significant except  uses  clauses.

MarkMLl
Title: Re: Class helpers
Post by: PascalDragon on May 02, 2019, 09:26:02 am
Two questions. The first: is there a restriction that a class can only be extended by a single helper, after which it's the helper that has to be extended? i.e.

A= class
B= class helper for A
C= class helper for A

is wrong, while

A= class
B= class helper for A
C= class helper(B) for A

is right? Alternatively, is there a rule that only one helper can be declared in a unit?

The problem that I was having was that when compiling using the Lazarus IDE it would fail at the first point it encountered a method from helper B, rather than getting into the unit B and saying "You can't declare C in here".
You can declare multiple helpers for a class, the compiler will simply pick the last one in scope. If you want to use multiple helpers you need to use inheritance.

Also the compiler would not be able to detect that you extend class A (declared in unit A) in both units B and C (without dependencies on eachother) as those units could be compiled independently (or be precompiled), not to mention that you could use unit B in unit X and unit C in Y those using different helpers for A inside the same binary.

Please note that we're planning to add support for multiple helpers to be active at once. You can keep track of the progress here (https://bugs.freepascal.org/view.php?id=35159).

The second: Can the definition of a class list the names of the units in which it may be extended by a helper?
Eh? What are you asking here?
Title: Re: Class helpers
Post by: MarkMLl on May 02, 2019, 10:24:19 am
You can declare multiple helpers for a class, the compiler will simply pick the last one in scope. If you want to use multiple helpers you need to use inheritance.

Also the compiler would not be able to detect that you extend class A (declared in unit A) in both units B and C (without dependencies on eachother) as those units could be compiled independently (or be precompiled), not to mention that you could use unit B in unit X and unit C in Y those using different helpers for A inside the same binary.

Please note that we're planning to add support for multiple helpers to be active at once. You can keep track of the progress here (https://bugs.freepascal.org/view.php?id=35159).

Thanks for those Sven. I'd picked up the dependencies issue but in this case both helpers were in the same unit which is why I was wondering whether there was a "one helper per unit" rule or something similar.

>> The second: Can the definition of a class list the names of the units in which it may be extended by a helper?
>
> Eh? What are you asking here?

Whether at the time of definition a class can have restrictions as to where helpers can be declared, e.g. that helpers for TObject can only be in known RTL units.

In practice I think that the existing dependency rules probably go a long way towards that, but I think that whether a chain of helpers can be sealed is still a fair question- and probably one that needs to be considered before any relaxation of the structure.

MarkMLl

Title: Re: Class helpers
Post by: Thaddy on May 02, 2019, 10:43:59 am
Of course, sealed classes can be extended by helpers. Example:
...
Sealing of classes are used only to block inheritance when creating a new data type (new class). Helpers are used to extend classes (and any other types of data), where inheritance does not occur at all, so the seal in this case does not matter.

Interestingly, it is not possible to seal the helper. Thus, each type can be extended with helpers, and their number is not limited. It is therefore possible to create a theoretically freely long chain of helper inheritance and extend the given type of data without end.

Thanks for that. I did say nobody had, and this was a few years ago :-)

I tend to look at things from the point of view of an OS writer, but I'd have thought that being able to forcibly terminate a helper chain would be useful. Or alternatively the second part of my original question, which was whether helpers could be rejected if they weren't in a list of specified units- which is probably infeasible, since I can't think of anywhere that a list of units is significant except  uses  clauses.

MarkMLl
Mark, I hope you understand class helpers are a bandage (except maybe for simple types) and not a cure....
Title: Re: Class helpers
Post by: MarkMLl on May 02, 2019, 12:27:42 pm
Mark, I hope you understand class helpers are a bandage (except maybe for simple types) and not a cure....

I understand you perfectly but I'm converting comms code written some time in the last millennium. It's thoroughly bloody, and I need every bit of help I can get :-)

Part of the problem is that it's got multiple UIs: GUI, internal interactive text and internal script which were OK under TP and later Delphi but not Lazarus. I was able to just about get any two of those three working without having to do major re-engineering, but the combination of a scripted activity being able to raise a GUI dialog(ue) box eventually forced me to move actual command execution into a thread. At that point it's convenient to be able to define the guts of the worker thread where the command is parsed, but to extend it using helpers for specific jobs (GUI manipulation, comms line handling). Each helper encapsulates Synchronize() calls and shim procedures, all heavily protected by assertions... I'd be the first to admit that it's hardly pretty but it works and keeps the main part of the worker thread fairly tidy.

MarkMLl
Title: Re: Class helpers
Post by: PascalDragon on May 03, 2019, 09:13:55 am
>> The second: Can the definition of a class list the names of the units in which it may be extended by a helper?
>
> Eh? What are you asking here?

Whether at the time of definition a class can have restrictions as to where helpers can be declared, e.g. that helpers for TObject can only be in known RTL units.

In practice I think that the existing dependency rules probably go a long way towards that, but I think that whether a chain of helpers can be sealed is still a fair question- and probably one that needs to be considered before any relaxation of the structure.

The purpose of helpers is to be able to add code to classes that you don't have direct access to. Adding some mechanism that would allow to restrict which units can extend that would be a contradiction to that purpose. Not to mention that in that supposed restriction clause you could enter basically anything, because the compiler can not check whether the unit exists (for example the System unit where TObject is declared is compiled without any other units involved).
Title: Re: Class helpers
Post by: PascalDragon on May 11, 2019, 02:41:12 pm
Trunk now also supports the modeswitch multihelpers which allows multiple helpers to be active at once for a single type. See also here (http://lists.freepascal.org/pipermail/fpc-pascal/2019-May/056015.html).
Title: Re: Class helpers
Post by: furious programming on May 12, 2019, 12:38:34 am
Trunk now also supports the modeswitch multihelpers which allows multiple helpers to be active at once for a single type.

Good news, thanks. However, there is still no possibility of creating a helper for classic objects, e.g. in this way:

Code: Pascal  [Select][+][-]
  1. type
  2.   TFoo = object
  3.   public
  4.     procedure Bar();
  5.   end;
  6.  
  7. type
  8.   TFooHelper = object helper for TFoo // Error: expected :, but for found
  9.   public
  10.     procedure Baz();
  11.   end;

Will it ever be supported?
Title: Re: Class helpers
Post by: PascalDragon on May 13, 2019, 09:14:14 am
Will it ever be supported?
Considering that I added support for interface type helpers as well, I could add support for object as well... You can add a bug report if you want...
as it is now I am able to inherit from an existing helper to add on features. From what I understand this is not allowed in
Delphi.

 In Delphi you can have only one helper and any additions must be done to that one helper where as in Fpc you can
inherit from an existing helper which I like a lot!
 :)
Delphi allows inheritance for class helper, but I saw no real reason to disallow it for record helper and type helper...  :-X
Title: Re: Class helpers
Post by: BeniBela on May 14, 2019, 12:46:38 am
I do not understand why there are so many different helpers

Why not call them all "type helper"  ?
Title: Re: Class helpers
Post by: furious programming on May 14, 2019, 02:04:13 am
Why not call them all "type helper"  ?

Hmm…

Code: Pascal  [Select][+][-]
  1. type
  2.   TFoo = class(TBar) // or any other type
  3.   {..}
  4.   end;
  5.  
  6. type
  7.   // instead of: TFooHelper = class helper for TFoo
  8.   TFooHelper = helper(TFoo)
  9.   {..}
  10.   end;
  11.  
  12. type
  13.   // instead of: TFooHelper2 = class helper(TFooHelper) for TFoo
  14.   TFooHelper2 = helper(TFooHelper)
  15.   // or
  16.   TFooHelper2 = helper(TFoo, TFooHelper)
  17.   {..}
  18.   end;

This should be enough. 8)
Title: Re: Class helpers
Post by: PascalDragon on May 14, 2019, 09:07:41 am
I do not understand why there are so many different helpers

Why not call them all "type helper"  ?
Because it was Delphi that introduced class helper and record helper. True, it was me that introduced type helper for primitive types, but with FPC 3.2.0 and newer it's possible to use type helper with classes and records as well, so for me type helper is the definite syntax for helper types (though the other two will be available no matter what).
Code: Pascal  [Select][+][-]
  1. type
  2.   TFoo = class(TBar) // or any other type
  3.   {..}
  4.   end;
  5.  
  6. type
  7.   // instead of: TFooHelper = class helper for TFoo
  8.   TFooHelper = helper(TFoo)
  9.   {..}
  10.   end;
  11.  
  12. type
  13.   // instead of: TFooHelper2 = class helper(TFooHelper) for TFoo
  14.   TFooHelper2 = helper(TFooHelper)
  15.   // or
  16.   TFooHelper2 = helper(TFoo, TFooHelper)
  17.   {..}
  18.   end;

This should be enough. 8)
True, that would be enough, but we'd need to support the other syntax anyway due to Delphi compatibility. The type helper syntax on the other hand is a close enough extension.
Title: Re: Class helpers
Post by: devEric69 on October 27, 2021, 09:32:48 am
(old post, but related)

I wanted to create a helper for TFoo (I know that it's not recommended), like this...:

Code: Pascal  [Select][+][-]
  1. type
  2.  
  3. class helper for TFoo
  4.    private
  5.       FiCount: integer;
  6.    public
  7.       function GetThing: TObject;
  8.   end;

... but, as soon as I've added the private field FiCount in my " helper for " the Class Tfoo, then, the compiler "cried", saying: "uHelperForFoo.pas(40,8) Error: An interface, helper or Objective-C protocol or category cannot contain fields".

So, is there a\another hacking \ bandage for TClasses in order to bypass this behavior (i.e. create private fields), or do I have to create a descendant i.e. Class(TFoo) (that said, I've got the source code of TFoo; so, it's mostly curiosity)?
Title: Re: Class helpers
Post by: PascalDragon on October 27, 2021, 09:37:32 am
So, is there a\another hacking \ bandage for TClasses in order to bypass this behavior (i.e. create private fields), or do I have to create a descendant i.e. Class(TFoo) (that said, I've got the source code of TFoo; so, it's mostly curiosity)?

You need to create a proper descendant and use that. There is no other way.
Title: Re: Class helpers
Post by: devEric69 on October 27, 2021, 10:09:46 am
Thank you.
TinyPortal © 2005-2018