Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2021-02

Traits Proposal

104 messages · 12 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Ryan Joseph

We had talked about this some time ago and it's been rattling around in my brain so I wanted to write it down into a formal proposal where we can discuss it and hopefully agree upon a syntax. Everything is preliminary and tentative but this is a syntax which allows a "composition over inheritance" model, also known as "mix-ins" in some languages. That idea is similar to multiple inheritance except you have a concrete reference to the trait being implemented so you can resolve conflicts easily.

Here's what I have so far. Please feel free to look at it and give any feedback.

https://github.com/genericptr/freepascal/wiki/Traits-Proposal

====================

type
  TSomeTrait = trait
    public
      field: integer;
      procedure DoThis;
  end;
  TMyClass = class
    private
      _trait: TSomeTrait;
    public
      property someTrait: TSomeTrait implements _trait;
  end;
var
  c: TMyClass;
begin
  c := TMyClass.Create;
  c.DoThis;
  writeln(c.field):
end;

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 5 lines
We had talked about this some time ago and it's been rattling around in my brain so I wanted to write it down into a formal proposal where we can discuss it and hopefully agree upon a syntax. Everything is preliminary and tentative but this is a syntax which allows a "composition over inheritance" model, also known as "mix-ins" in some languages. That idea is similar to multiple inheritance except you have a concrete reference to the trait being implemented so you can resolve conflicts easily. Here's what I have so far. Please feel free to look at it and give any feedback. https://github.com/genericptr/freepascal/wiki/Traits-Proposal

In general, I am charmed by this idea.

But I don't see the need to extend/change existing syntax, and in fact I highly object to it.

Instead of trait you might as well use simply "object", so basically we could simply state that instead of an interface, you can also specify an object in the class definition:

TMyClass = class (TParent,TMyTrait1,TMyTrait2)
Public
   property Trait1: TMyTrait1 read FTrait1 implements TMyTrait1;
   property Trait2: TMyTrait2 read FTrait2 implements TMyTrait2;
end;

So no syntax changes are needed. Any restrictions on the objects in the "traits" can be handled by the compiler.

The only difference with Interfaces is that you MUST have a property with 'Implements' if you specify an object. If you do not, the compiler complains.

It will also automatically mean that you can only have 1 trait of a certain type, since the
TMyClass = class (TParent,TMyTrait1,TMyTrait2)
cannot contain duplicates.

I also don't think you should forbid constructors; In fact I think they are necessary if you want to support polymorphism, because I think they create/set the VMT ? (the compiler people need to confirm this)

The only obvious thing missing in your proposal is how this changes the RTTI of the objects.

Michael.

Reply by email

Adriaan van Os
to Michael Van Canneyt
Michael Van Canneyt wrote 19 lines
On Tue, 9 Feb 2021, Ryan Joseph via fpc-pascal wrote: > We had talked about this some time ago and it's been rattling around > in my brain so I wanted to write it down into a formal proposal where > we can discuss it and hopefully agree upon a syntax. Everything is > preliminary and tentative but this is a syntax which allows a > "composition over inheritance" model, also known as "mix-ins" in some > languages. That idea is similar to multiple inheritance except you > have a concrete reference to the trait being implemented so you can > resolve conflicts easily. > > Here's what I have so far. Please feel free to look at it and give any > feedback. > > https://github.com/genericptr/freepascal/wiki/Traits-Proposal In general, I am charmed by this idea.

I am, in whatever form it is implemented, also a proponent of the basic idea behind it, formulated on Ryan's page as:

"Traits allow a "composition over inheritance" model which complement the existing single inheritance model introduced in Object Pascal. Traits are similar to multiple inheritance in languages such as C++ and Java, with the difference being an explicit reference to the class (or trait) which is being implemented."

Regards,

Adriaan van Os

Reply by email

Ryan Joseph
to Michael Van Canneyt: In general, I am charmed by this idea. But I don't see the need to extend/change existing…
Show quoted text 13 lines
On Feb 10, 2021, at 12:32 AM, Michael Van Canneyt via fpc-pascal wrote: Instead of trait you might as well use simply "object", so basically we could simply state that instead of an interface, you can also specify an object in the class definition: TMyClass = class (TParent,TMyTrait1,TMyTrait2) Public property Trait1: TMyTrait1 read FTrait1 implements TMyTrait1; property Trait2: TMyTrait2 read FTrait2 implements TMyTrait2; end;

Here's the old thread http://free-pascal-general.1045716.n5.nabble.com/Interface-delegates-and-the-implements-property-specifier-td5734729.html#a5734741. I think we agreed on introducing "trait" because it makes it clear that they are not objects and have other limitations. Under the hood they will be objects but it's important that the new type exists so the compiler can limit certain feature, like operator overloads and constructors (see below).

What does putting them in the class hierarchy gain us? implementing duplicate traits is always going to be an error one way or another. I guess it's nice to see in the class 'll wait to hear what the compiler people think about this.

Again I think it was requested to implement traits using properties but in the simplest form all the compiler really needs to know is that 1) there is a field in the class and 2) the field if of type "trait". Even this would be good enough in theory:

type
  TMyClass = class
    FTrait: TSomeTrait;
  end;
I also don't think you should forbid constructors; In fact I think they are necessary if you want to support polymorphism, because I think they create/set the VMT ? (the compiler people need to confirm this)

I think the VMT is created by adding virtual to a method in the class hierarchy. This is the only potentially complicated part of the feature and one I have lots of questions about (see my notes on the git page).

Constructors don't make sense because traits can never be instantiated by the programmer. There could be lifetime hooks, like AfterImplemented(parent: TObject) that the compiler calls automatically when a class is instantiated (like AfterConstruction that all Object classes can override). In fact traits should probably have a root class which is hidden, just like TObject.

The only obvious thing missing in your proposal is how this changes the RTTI of the objects.

Good question. My plan currently is to make trait fields be implemented via automatically generated properties simply because you get lots of free functionally.

For methods I'm going to leverage what I'm learning from implementing "implicit function specialization", which is finally getting reviewed and moving forward after more than a year of limbo. The basic idea of overloading is the same so it will be easy enough for me to hook this up. The methods don't really exist however so they won't exist in the RTTI. Overriding methods will have bodies and probably work with the RTTI.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 17 lines
> On Feb 10, 2021, at 12:32 AM, Michael Van Canneyt via fpc-pascal wrote: > > Instead of trait you might as well use simply "object", so basically we > could simply state that instead of an interface, you can also specify an object > in the class definition: > > > TMyClass = class (TParent,TMyTrait1,TMyTrait2) > Public > property Trait1: TMyTrait1 read FTrait1 implements TMyTrait1; > property Trait2: TMyTrait2 read FTrait2 implements TMyTrait2; > end; > Here's the old thread http://free-pascal-general.1045716.n5.nabble.com/Interface-delegates-and-the-implements-property-specifier-td5734729.html#a5734741. I think we agreed on introducing "trait" because it makes it clear that they are not objects and have other limitations. Under the hood they will be objects but it's important that the new type exists so the compiler can limit certain feature, like operator overloads and constructors (see below).

I heavily object to this; We have way too much keywords as it is already. So unless there really is no other way I don't think we should introduce even more.

The compiler can do the check on an object for allowed/disallowed features as soon as it is used in a 'trait'. So there really is no advantage gained by using 'trait'. The check just happens in a different place: not in the object definition, but when it is used as a trait. At that point you must check for duplicate names (and probably other things) anyway.

Using 'object' has the additional advantage that you can simply use existing objects, if they fulfill any restrictions, as traits.

If you impose the use of 'trait' you risk that you must define objects twice to be able to use them as a trait - once standalone, once as a trait.

In Javascript you also don't need to use special classes/objects for mixins.

I really do not see a good reason to introduce a new type and matching keyword.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Feb 10, 2021, at 8:41 AM, Michael Van Canneyt wrote: I heavily object to this; We have way too much keywords as it is already. So unless there really is no other way I don't think we should introduce even more.

I guess the first question is whether a "trait" is a new construct or just a way to import fields/methods from a class. I like the idea of a formal new type so we can limit the scope of the feature instead of taking on the baggage of an old type like "object". I'll wait to hear from the compiler team however.

Looking back at the old thread Sven said:

"What I'm still missing however is a real use case. What you have presented as an example can just as easily be done with the existing delegates. And just to avoid having to cast the instance to the interface is in my opinion not enough reason for a new feature. "

so the idea wasn't rejected outright but I'm not sure if this idea even had their consent. I think traits/mixins are the way of the future for OOP but that's not certain for FPC yet. For example C# is a major language which is still single-inheritance only.

btw, here are some links I've gathered on how other languages support this concept:

• PHP (trait): https://www.php.net/manual/en/language.oop5.traits.php • Python (mixin): https://devtut.github.io/python/mixins.html#mixin • Swift (default protocol):
• https://nshipster.com/swift-default-protocol-implementations/ • http://machinethink.net/blog/mixins-and-traits-in-swift-2.0/

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 12 lines
> On Feb 10, 2021, at 8:41 AM, Michael Van Canneyt wrote: > > I heavily object to this; We have way too much keywords as it is already. So unless there really is no other way I don't think we should introduce > even more. I guess the first question is whether a "trait" is a new construct or just a way to import fields/methods from a class. I like the idea of a formal new type so we can limit the scope of the feature instead of taking on the baggage of an old type like "object". I'll wait to hear from the compiler team however.

I don't see what baggage there is ?

You said it yourself: internally it will just be an object. Just make it formally so.

I also think the argument of reusing existing objects deserves consideration.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
Show quoted text 5 lines
On Feb 10, 2021, at 9:06 AM, Michael Van Canneyt wrote: You said it yourself: internally it will just be an object. Just make it formally so. I also think the argument of reusing existing objects deserves consideration.

Is it strange that there are extra things in the object which aren't related to the trait? You could declare destructors/constructors and maybe function modifiers that don't make sense in modern code. I don't have a strong argument I guess except it feels clean to start with a fresh slate. I'm also assuming that during development we'll find problems with how to handle feature X of objects which are conflicting somehow and it would be easier to have a new type to resolve this. I'll wait to hear from others.

Still, the biggest hurdle is getting any acceptance from the compiler team. I really don't like the interface delegation pattern (from Delphi?) because it doesn't help with namespaces but as Sven suggested this should be good enough for us. Traits are easy to understand and implement but the interface delegation requires tons of boiler plate and you still need to subscript into classes instead of getting a nice unified namespace.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph: I guess the first question is whether a "trait" is a new construct or just a way to…
Ryan Joseph wrote

The example exposes another aspect:

|trait SayWorld {     public function sayHello() { parent::sayHello();|

|In this case the trait has access to the object/class into which it is embedded. (otherwise it could not call the inherited method of the outer class).

Is that wanted?
(methods and fields?)

If that is wanted....
- If it will be using the existing "object" (or advanced record), then such code is not possible.
- One might have to think about how to declare what a trait can access?    Just allow anything in the code, and attempt to resolve when the trait is embedded? IMHO maybe not?

|

Reply by email

Ryan Joseph
to Martin Frb
Show quoted text 17 lines
On Feb 10, 2021, at 10:17 AM, Martin Frb via fpc-pascal wrote: The example exposes another aspect: > trait SayWorld { > public function sayHello() { > parent::sayHello(); In this case the trait has access to the object/class into which it is embedded. (otherwise it could not call the inherited method of the outer class). Is that wanted? (methods and fields?) If that is wanted.... - If it will be using the existing "object" (or advanced record), then such code is not possible. - One might have to think about how to declare what a trait can access? Just allow anything in the code, and attempt to resolve when the trait is embedded? IMHO maybe not?

Here's an example of how this could work. "parent" could in theory be a hidden field which the compiler sets for you when the class implementing the trait is instantiated.

If there was no virtual/override then there would be no VMT and normal casting would work to call the base class method.

This is the area I'm not sure about yet, i.e. VMT table and overriding. There are some examples on the wiki also.

====================

type
 TSomeTrait = trait
   public
     parent: TObject;
     procedure DoThis;
 end;
 procedure TSomeTrait .DoThis;
 begin
    // ??? here is our issue. Is this good enough to call the TBaseClass.DoThis?
    TBaseClass(parent).DoThis;
 end;
 TBaseClass = class
    procedure DoThis; virtual;
 end;
 TMyClass = class(TBaseClass)
   private
     _trait: TSomeTrait;
   public
     property someTrait: TSomeTrait implements _trait;
     // we need override because TBaseClass.DoThis is virtual.
     // The compiler can generate the body for us if we don't implement it.
     procedure DoThis; override;
     procedure AfterConstruction; override;
 end;
procedure TMyClass .AfterConstruction;
begin
  someTrait.parent := self;
end;
var
 c: TMyClass;
begin
 c := TMyClass.Create;
 c.DoThis;
end;

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph
Show quoted text 14 lines
On Feb 10, 2021, at 11:09 AM, Ryan Joseph wrote: type TSomeTrait = trait public parent: TObject; procedure DoThis; end; procedure TSomeTrait .DoThis; begin // ??? here is our issue. Is this good enough to call the TBaseClass.DoThis? TBaseClass(parent).DoThis; end;

Thinking about this more I don't think there's even a reason for it since Object Pascal doesn't let you do stuff like this anyways. If you want to call the super class you need to use "inherited" from within the class body. The example I posted only works if there is no virtual/override involved.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph
Ryan Joseph wrote 17 lines
> On Feb 10, 2021, at 11:09 AM, Ryan Joseph wrote: > > type > TSomeTrait = trait > public > parent: TObject; > procedure DoThis; > end; > > procedure TSomeTrait .DoThis; > begin > // ??? here is our issue. Is this good enough to call the TBaseClass.DoThis? > TBaseClass(parent).DoThis; > end; Thinking about this more I don't think there's even a reason for it since Object Pascal doesn't let you do stuff like this anyways. If you want to call the super class you need to use "inherited" from within the class body. The example I posted only works if there is no virtual/override involved.

Its not about the keyword to be used.
Its not even about the inheritance.

In the example from https://www.php.net/manual/en/language.oop5.traits.php The trait accesses a method defined in the class to which the trait is applied. (It happens to be from the base class, but that does not matter)

In Pascal that would look like

type
   TSomeTrait = trait
   public
     procedure DoTraitFoo;
   end;
   TSomeClass = class(TObject)
   private
     trait: TSomeTrait; // whatever syntax is used so that the trait is
added
   public
     SomeVal: Integer;
     procedure DoSome;
     procedure Dispatch(var message); override;
   end;
procedure TSomeTrait.DoTraitFoo;
begin
   inherited Dispatch(nil); // from TObject
   DispatchStr('');  // from TObject
   DoSome;  // from SomeClass
   SomeVal := 1;  // if we can access methods, then why not data
end;

In the example
|  parent::sayHello();|
"parent" does not refer to the class containing the trait. "parent" = "inherited"

The php example tait's sayHello hides the inherited sayHello => so parent is needed.

So in the example this/self of the embedded trait is the entire object into which it is embedded.

That means also: the trait and the class must be conflictfree even for  private variables. (except where maybe cross unit scoping hides them???)

-----------------
Leaving scoping/conflicts aside.

In Pascal TSomeTrait as it is given above can not compile, as it does not know what DoSome,SomeVal,Dispatch are. Not until it is embedded. Well it could compile as a generic...

So, if a trait should have such access, then how to make sure the trait can compile?
Maybe:

   TSomeTrait = trait
   imports
// Those must be implement/provided by classes to which the trait is added.
     procedure DoSome;
     SomeVal : integer;
   public
     procedure DoTraitFoo;
   end;

Or (less flexible)
  TSomeTrait = trait
  extends(TSomeClass)  // can extend anything that is inherited from TSomeClass (i.e can access anything that a TSomeClass has)   public
    procedure DoTraitFoo;
  end;

Reply by email

Ryan Joseph
to Ryan Joseph: Thinking about this more I don't think there's even a reason for it since Object Pascal…
On Feb 10, 2021, at 12:17 PM, Ryan Joseph wrote: Thinking about this more I don't think there's even a reason for it since Object Pascal doesn't let you do stuff like this anyways. If you want to call the super class you need to use "inherited" from within the class body. The example I posted only works if there is no virtual/override involved.

Also, PHP doesn't have function overloading so they need to add all these complicated conflict resolution syntaxes into their implementation. For Pascal we don't have this problem and we can simply rely on the existing rules. The only exception is when you override a method that is implemented as a trait (see the "Overrides" section in the wiki). We may not even want to allow this because it's an edge case that could be resolved by explicit references. Traits can be VERY simple if we keep it that way.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Martin Frb: Its not about the keyword to be used. Its not even about the inheritance. In the example…
Show quoted text 16 lines
On Feb 10, 2021, at 12:40 PM, Martin Frb via fpc-pascal wrote: type TSomeTrait = trait public procedure DoTraitFoo; end; TSomeClass = class(TObject) private trait: TSomeTrait; // whatever syntax is used so that the trait is added public SomeVal: Integer; procedure DoSome; procedure Dispatch(var message); override; end;

if it's the simple then just set a reference to TSomeClass inside of TSomeTrait. If the trait doesn't know anything about the implementor then you indeed need to use a dispatch table or something. Can't this be solved with existing features?

btw, I don't think we should model this based on PHP, I just gave that as an example. The Swift model is much more simple and does 99% of what you want. I think that's what we should do for Pascal also.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph
Ryan Joseph wrote 21 lines
> On Feb 10, 2021, at 12:40 PM, Martin Frb via fpc-pascal wrote: > > type > TSomeTrait = trait > public > procedure DoTraitFoo; > end; > > TSomeClass = class(TObject) > private > trait: TSomeTrait; // whatever syntax is used so that the trait is added > public > SomeVal: Integer; > procedure DoSome; > procedure Dispatch(var message); override; > end; if it's the simple then just set a reference to TSomeClass inside of TSomeTrait. If the trait doesn't know anything about the implementor then you indeed need to use a dispatch table or something. Can't this be solved with existing features? btw, I don't think we should model this based on PHP, I just gave that as an example. The Swift model is much more simple and does 99% of what you want. I think that's what we should do for Pascal also.

I don't have a pro/contra agenda on whether that feature should be part of the fpc trait or not.
I just saw it, and thought I raise it.

But also, if any option should be reserved for the future, then it matters. Because re-using "sometrait = object end" makes it less likely that an "implements" clause will be added (because "object" does already have a fully defined syntax).
So I felled it might be worth being noted.

------
Another option would be

      TSomeTrait = trait (basetrait) for (someclass_as_minimum_base_class)

for (someclass) is of course  optional (or rather default to TObject). So sometrait then can access
   self.any_trait_identifier
   self.someclass_ident

This is a bit limiting at first, but bring in genericts       generic TSomeTrait = trait (basetrait) for (<T>) Since it is a generic the compiler will not complain about unknown identifiers.

It can automatically specialise, when applying a trait.

Then again the "implements" clause from above could internally create a generic too.

----------------------
Btw, next question that just came up

Will traits only apply to classes?

or could a record / advanced record / old object also receive traits ?

------------------------
And one more thing that may at least be considered for design in case of future use.

Consider a Trait
TEmployeList = trait
   function  FindByUnallocateWorkTime: TEmploye;
end;
TCompany = class
end;

Now the company could have Engineers and Designers. It needs to lists.... But the trait can obviously only be added once.
Yet no-one wants to copy and paste the trait to create a 2nd verson

Maybe Something like
TDesignerList = trait(TEmployeList)
  function  FindDesigerByUnallocateWorkTime: TEmploye; aliases FindByUnallocateWorkTime;
end;

--------------
Again, none of those are needed. But if they are found interesting....

Reply by email

Ryan Joseph
to Martin Frb
Show quoted text 5 lines
On Feb 10, 2021, at 1:51 PM, Martin Frb via fpc-pascal wrote: I don't have a pro/contra agenda on whether that feature should be part of the fpc trait or not. I just saw it, and thought I raise it.

These issues you are raising are more related to general type safety/compatibility and probably beyond the scope of what traits should do. It also introduces lots of new concepts and syntax which make this exponentially more unlikely to get approved.

The only purpose of the trait is import fields/methods into a namespace of a struct, like multiple inheritance but with references. We should keep it as simple as possible imo.

Show quoted text 7 lines
---------------------- Btw, next question that just came up Will traits only apply to classes? or could a record / advanced record / old object also receive traits ?

I don't see why not but nothing is settled yet.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ryan Joseph
On Feb 10, 2021, at 2:42 PM, Ryan Joseph wrote: We should keep it as simple as possible imo.

It's not as elegant as other ideas we may think of but I think it's good enough to let users write boilerplate methods to resolve naming conflicts. We don't need to introduce new syntax or concepts. This is the power of traits vs multiple inheritance. We get the shared namespace like normal OOP inheritance but have full control over any conflicts that arise by mixing in traits.

====================

type
  TTraitA = trait
    procedure DoThis;
  end;
  TTraitB = trait
    procedure DoThis;
  end;
type
  TMyClass = class
    private
      property traitA: TTraitA implements _traitA;
      property traitB: TTraitB implements _traitB;
    public
      // DoThis now hides traitB.DoThis like it would in normal Object Pascal
      procedure DoThis;
  end;
procedure TMyClass.DoThis;
begin
  // resolve the conflict by directly referencing the trait
  traitA.DoThis;
end;

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Martin Frb: The example exposes another aspect: |In this case the trait has access to the…
On Feb 10, 2021, at 10:17 AM, Martin Frb via fpc-pascal wrote: - One might have to think about how to declare what a trait can access? Just allow anything in the code, and attempt to resolve when the trait is embedded? IMHO maybe not?

Sorry Martin you said a lot and I missed this. :) The plan is to only export fields/methods in the public section.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph: It's not as elegant as other ideas we may think of but I think it's good enough to let…
Ryan Joseph wrote
We get the shared namespace

So basically, we get the compiler to automatically create forwarder methods for us (or at least that behaviour). And that is all there is. right?

In that case IIRC, it was said traits are not allowed constructors. Why? Now a constructor would and should not have a forwarding method, but that the compiler can skip.
Yet, in my code, in the constructor of the TMyClass, I could add   _traitA.Create
even
  _traitA := TTraitA.Create; // if the trait was not an object, but another class.

The compiler can with no doubt create the forwarder methods. And if I understand right, then that is all the compiler should do? (Not that I advertise a trait should be a class / IMHO better not)

Show quoted text 6 lines
procedure TMyClass.DoThis; begin // resolve the conflict by directly referencing the trait traitA.DoThis; end;

This we already can do. Write our own forwarder method. I understand it is for conflict resolution only. But see my example => as soon as you need to repeat a trait with just a change in name, you always need conflict resolution.
IMHO, there are many traits that a class could need more than once.

Also that only works, if a trait is included as a field. There was at least one message in this mail-thread that suggested   TMyClass = class(TObject, TTraitA);

The solution is on
https://en.wikipedia.org/wiki/Trait_(computer_programming)

/alias/: an operation that creates a new trait by adding a new name for an existing method

Doing that for all methods, means having 2 identical traits, but with different names. => no conflict.

Reply by email

Ryan Joseph
to Martin Frb
On Feb 10, 2021, at 7:47 PM, Martin Frb via fpc-pascal wrote: In that case IIRC, it was said traits are not allowed constructors. Why?

Traits are meant to be a way to import fields/methods and is based off of "object" (so it's on the stack). This is also important for using properties to alias the imported fields. It's the same with subclassing, you don't need to manually allocate the super class because it's all one structure with a shared memory space. The idea is to make a viable alternative to inheritance in the simplest way possible.

Show quoted text 11 lines
> procedure TMyClass.DoThis; > begin > // resolve the conflict by directly referencing the trait > traitA.DoThis; > end; > > This we already can do. Write our own forwarder method. I understand it is for conflict resolution only. But see my example => as soon as you need to repeat a trait with just a change in name, you always need conflict resolution. IMHO, there are many traits that a class could need more than once.

I know it's not the most elegant but we need to keep this simple if there's going to be any chance of it even being considered. I will let the compiler team defer to this.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Martin Frb: So basically, we get the compiler to automatically create forwarder methods for us (or at…
On Feb 10, 2021, at 7:47 PM, Martin Frb via fpc-pascal wrote: I understand it is for conflict resolution only. But see my example => as soon as you need to repeat a trait with just a change in name, you always need conflict resolution.

Please post the code snippet again. I'm not sure I understand what you're saying. The most important thing here is that we don't allow the programmer to write ambiguous code. If all else fails the code must fail to compiler, if there's any chance of any conflict whatsoever. The first reason this idea gets shot down is because people think of crazy "with" statement bugs and we need to avoid that at all costs.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph
Ryan Joseph wrote 5 lines
> On Feb 10, 2021, at 7:47 PM, Martin Frb via fpc-pascal wrote: > > I understand it is for conflict resolution only. But see my example => as soon as you need to repeat a trait with just a change in name, you always need conflict resolution. Please post the code snippet again. I'm not sure I understand what you're saying.
TEmployeList = trait
   function  FindByUnallocateWorkTime: TEmploye;
   // other functions/members
end;
TCompany = class
end;

Now the company could have Engineers and Designers. It needs to lists.... But the trait can obviously only be added once. Yet no-one wants to copy and paste the trait to create a 2nd verson

Maybe Something like  (aliasing / no need to copy implementation) TDesignerList = trait(TEmployeList)
  function  FindDesigerByUnallocateWorkTime: TEmploye; aliases FindByUnallocateWorkTime;  // no body /just a name replacement   // and the others
end;

===>
TCompany = class
   _TraitDesigner: TDesignerList ;
   _TraitEngeneers: TEmployeList;
end;

This is clearly something that can be done later. **IF** the design is chosen to allow for it.

Reply by email

Ryan Joseph
to Martin Frb
Show quoted text 7 lines
On Feb 10, 2021, at 9:05 PM, Martin Frb via fpc-pascal wrote: Maybe Something like (aliasing / no need to copy implementation) TDesignerList = trait(TEmployeList) function FindDesigerByUnallocateWorkTime: TEmploye; aliases FindByUnallocateWorkTime; // no body /just a name replacement // and the others end;

I see. I used "override from" in the wiki. From my experience I don't think the compiler team will allow this since it adds news syntax and complexity so I suggest we focus on manual boilerplate functions for now. I will of course defer to them on these matters.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph: Traits are meant to be a way to import fields/methods and is based off of "object" (so…
Ryan Joseph wrote 5 lines
> On Feb 10, 2021, at 7:47 PM, Martin Frb via fpc-pascal wrote: > > In that case IIRC, it was said traits are not allowed constructors. Why? Traits are meant to be a way to import fields/methods and is based off of "object" (so it's on the stack). This is also important for using properties to alias the imported fields. It's the same with subclassing, you don't need to manually allocate the super class because it's all one structure with a shared memory space. The idea is to make a viable alternative to inheritance in the simplest way possible.

"don't need to manually allocate"
I know, I am using old "object" for that already. So that is not new when traits come.

Hence I concluded, the "trait" feature is exactly and only an automated generation of forwarder-methods (well exactly they are no longer forwarders, but imported / to the user that writes a call to them that is the same in the end)

But if the above conclusion is correct (no more, no less that replacing the need for forwarders ), then it is reasonable to want the same concept for other types... I do not actually advocate it. I simple point out what would logically follow (rather than what may or may not be good)

Reply by email

Sven Barth
to Ryan Joseph: We had talked about this some time ago and it's been rattling around in my brain so I…
Ryan Joseph wrote 5 lines
We had talked about this some time ago and it's been rattling around in my brain so I wanted to write it down into a formal proposal where we can discuss it and hopefully agree upon a syntax. Everything is preliminary and tentative but this is a syntax which allows a "composition over inheritance" model, also known as "mix-ins" in some languages. That idea is similar to multiple inheritance except you have a concrete reference to the trait being implemented so you can resolve conflicts easily. Here's what I have so far. Please feel free to look at it and give any feedback. https://github.com/genericptr/freepascal/wiki/Traits-Proposal

Time for me to tackle this topic as well. First of, I might incorporate answers to other mails of this thread here (directly or indirectly), just so you know.

Right now, Ryan, your suggestion looks like a solution in search of a problem, or a "hey, look at that feature in language X, I so must have that in Pascal as well". Those concepts more likely then not tend to end in problems or should be rejected. So let's first define what we're trying to solve here:

Allow the extension of a class' declaration with preexisting behavior AND state through the use of composition instead of inheritance, providing this in a convenient and possible even transparent way to the user of the class while allowing this to be implemented conveniently by the author of the class.

Let's break this down and let's see whether we can naturally evolve existing language features to cover this instead of trying to shoehorn concepts of other languages for this. Obviously I know where I'm going with this, but bear with me.

So the first part: "Allow the extension of a class' declaration with preexisting behavior AND state through the use of composition instead of inheritance". Obviously this is already possible in Object Pascal, namely by using the interface delegation feature. For those that aren't aware of the full capabilities of this feature, here is a small excursion (if you think you already know all there is to know, jump ahead to "end of the delegate excursion"):

The interface delegation feature allows the developer of a class to redirect the implementation of a specific interface to a class or interface instance provided in the class. A simple case looks like this:

=== code begin ===

type
   ITest = interface
     procedure Test;
   end;
   TTest = class(TObject, ITest)
   private
     fTest: ITest;
   public
     property TestImpl: ITest read fTest implements ITest;
   end;

=== code end ===

This is however not the full ability of this feature. The property implementing this interface does not need to be an interface. It can also be a class instance and this class instance does *not* need to declare that it implements this interface. So the following is a valid interface delegation as well (Note: FPC does not support this currently, that is still an outstanding Delphi compatibility problem):

=== code begin ===

type
   ITest = interface
     procedure Test;
   end;
   TTestImpl = class
     procedure Test;
   end;
   TTest = class(TInterfacedObject, ITest)
   private
     fTest: TTestImpl;
   public
     property TestImpl: TTestImpl read fTest implements ITest;
   end;

=== code end ===

In this case the methods introduced by IInterface are implemented by TTest's parent class and ITest.Test is implemented by the TTestImpl instance. If TTestImpl inherits from e.g. TInterfacedObject then TTest does not need to inherit from TInterfacedObject itself (Note: the reference counting in relation to interface delegation and aggregation is a whole topic in and of itself and will be ignored here).

One can even control which methods should be implemented by the property:

=== code begin ===

type
   ITest = interface
     procedure Test1;
     procedure Test2;
   end;
   TTestImpl = class
     procedure Test1;
   end;
   TTest = class(TInterfacedObject, ITest)
   private
     fTest: TTestImpl;
   public
     procedure ITest.Test2 = MyTest2; // name can be chosen freely, can
also be "Test2"
     property TestImpl: TTestImpl read fTest implements ITest;
     procedure MyTest2;
   end;

=== code end ===

So far this only covered behavior, but as interfaces can also provide properties it's also possible to provide state through them (though they *have* to be implemented through explicit Getters and Setters):

=== code begin ===

type
   ITest = interface
     function GetTestProp: LongInt;
     procedure SetTestProp(aValue: LongInt);
     property TestProp: LongInt read GetTestProp write SetTestProp;
   end;
   TTestImpl = class
     function GetTestProp: LongInt;
     procedure SetTestProp(aValue: LongInt);
   end;
   TTest = class(TInterfacedObject, ITest)
   private
     fTest: TTestImpl;
   public
     property TestImpl: TTestImpl read fTest implements ITest;
   end;

=== code end ===

For using a delegated interface you need to cast the class instance to the desired interface type:

=== code begin ===

var
   t: TTest;
   i: ITest;
begin
   t := TTest.Create;
   try
     i := t as ITest;
     i.Test1;
   finally
     t.Free;
   end;
end.

=== code end ===

In all cases the compiler generates interface thunks that ensure that the correct methods are executed (this is what needs to be implemented in FPC to allow the use of class types instead of only interface types).

With this we have the end of the delegate excursion.

Thus the part of adding state and behavior can already be done through interface delegation.

The next part: "providing this in a convenient and possible even transparent way to the user of the class"

Now as we've seen above this is not the case right now with interface delegation as one needs to cast to the interface type to allow this. However there already is a way to allow for a more convenient use of a property, namely the "default" modifier for indexed properties. Thus it would be a logical extension to allow the "default" modifier for "implements" as well. In this way the compiler would "hoist" the methods/properties of the specified interface into the namespace of the implementing class as well (while making sure that there is no ambigious case of course).

=== code begin ===

type
   ITest = interface
     procedure Test;
   end;
   TTest = class(TObject, ITest)
   private
     fTest: ITest;
   public
     property Test: ITest read fTest implements ITest; default;
   end;
var
   t: TTest;
begin
   t := TTest.Create;
   try
     t.Test; // calls t.fTest.Test
   finally
     t.Free;
   end;
end.

=== code end ===

This way we can easily provide even a transparent way for the user to use a class with a delegated interface.

Now the last part: "while allowing this to be implemented conveniently by the author of the class."

As seen above the class needs to declare that it provides an interface and the "implements" property either needs to be a interface or class instance.

Now the later could be solved rather easily thanks to class instances already not having to implement the interface: one can easily allow objects and records to "implement" such an interface through a "implements" property as well:

=== code begin ===

type
   ITest = interface
     procedure Test;
   end;
   TTestImpl = record
     procedure Test;
   end;
   TTest = class(TObject, ITest)
   private
     fTest: TTestImpl;
   public
     property Test: TTestImpl read fTest implements ITest; default;
   end;
var
   t: TTest;
begin
   t := TTest.Create;
   try
     t.Test; // calls t.fTest.Test
   finally
     t.Free;
   end;
end.

=== code end ===

As the compiler needs to generate corresponding thunks anyway whether it needs to do this for a record or object is not really much more effort either.

Whether the class needs to declare the interface in its interface clause can be argued about.

But all in all one can see that with a few extensions of existing features one can easily provide a mixin-like, convenient functionality.

Of course this does not provide any mechanism to directly add fields, however the compiler could in theory optimize access through property getters/setters if they're accessed through the surrounding class instance instead of the interface.

Also this does not address the point of whether these delegates are able to access functionality of the surrounding class. In my opinion however this can be explicitely modelled by providing the class instance through a constructor or property or whatever.

So there you have it, my two cents (well, more like a few euros :P ) regarding the trait proposal.

Regards,
Sven

Reply by email

gabor
to Sven Barth

W dniu 2021-02-13 o 20:38, Sven Barth via fpc-pascal pisze:

Of course this does not provide any mechanism to directly add fields, however the compiler could in theory optimize access through property getters/setters if they're accessed through the surrounding class instance instead of the interface.

So the complement could be generic classes like this:

type
   generic TTestGeneric = class(<TAncenstorClass>, ITest, IInterface2)
   private
     fTest: TTestImpl;
   public
     property Test: TTestImpl read fTest implements ITest; default;
   end;
   TMyTest = specialize TTestGeneric(<TInheritedCalssOfTAncesot>);

Something similar has already been asked:

Michał.

Reply by email

Ben Grasset
to Ryan Joseph: We had talked about this some time ago and it's been rattling around in my brain so I…

This seems possibly a *little* too similar to the existing Interface type in Object Pascal, however, I *would* really like to see some kind of functionality that basically amounts to "has the same capabilities as Interfaces and works on records and objects too, but does NOT require any kind of heap allocation".

So whether it be this, or just an improvement on the Interfaces we already have, I'd definitely personally be in favor of something that "works like Interfaces except minus the negative performance implications."

On Tue, Feb 9, 2021 at 9:27 PM Ryan Joseph via fpc-pascal <

Show quoted text 46 lines
wrote: We had talked about this some time ago and it's been rattling around in my brain so I wanted to write it down into a formal proposal where we can discuss it and hopefully agree upon a syntax. Everything is preliminary and tentative but this is a syntax which allows a "composition over inheritance" model, also known as "mix-ins" in some languages. That idea is similar to multiple inheritance except you have a concrete reference to the trait being implemented so you can resolve conflicts easily. Here's what I have so far. Please feel free to look at it and give any feedback. https://github.com/genericptr/freepascal/wiki/Traits-Proposal ==================== type TSomeTrait = trait public field: integer; procedure DoThis; end; TMyClass = class private _trait: TSomeTrait; public property someTrait: TSomeTrait implements _trait; end; var c: TMyClass; begin c := TMyClass.Create; c.DoThis; writeln(c.field): end; Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Sven Barth: Time for me to tackle this topic as well. First of, I might incorporate answers to other…
On Feb 13, 2021, at 12:38 PM, Sven Barth wrote: Right now, Ryan, your suggestion looks like a solution in search of a problem, or a "hey, look at that feature in language X, I so must have that in Pascal as well". Those concepts more likely then not tend to end in problems or should be rejected. So let's first define what we're trying to solve here:

What I'm trying to solve is the dilemma of OOP where we want to extend a class but the addition does not fit cleanly into an existing hierarchy. I know we've all had this problem and we were forced to settle on injecting some base class into a hierarchy even though other classes higher up don't need this functionality. Things like class helpers and interface delegation have already been added into the language so clearly there is a need not being addressed. We've all had this problem haven't we?

First off lets go summarize the differences/limitations of interface delegation (as it is now) when compared to good, old fashion normal inheritance and OOP:

1) Does not support fields, class operators, properties
2) Only supports classes so we're required to manage memory (more fragmented also which could be bad)
3) Requires you to define an interface with duplicate methods, so boilerplate code
4) Does not combine namespaces so you must cast the class as the interface or use the reference directly using dot notation (with performance implications?)

inheritance is so much easier and powerful. Just add the class name () and you're good to go. That's my starting point, what ever solution should be close to as easy as what already exists in OOP.

Show quoted text 36 lines
=== code begin === type ITest = interface procedure Test; end; TTestImpl = record procedure Test; end; TTest = class(TObject, ITest) private fTest: TTestImpl; public property Test: TTestImpl read fTest implements ITest; default; end; var t: TTest; begin t := TTest.Create; try t.Test; // calls t.fTest.Test finally t.Free; end; end. === code end === As the compiler needs to generate corresponding thunks anyway whether it needs to do this for a record or object is not really much more effort either. Whether the class needs to declare the interface in its interface clause can be argued about. But all in all one can see that with a few extensions of existing features one can easily provide a mixin-like, convenient functionality.

Great, this is getting closer to inheritance. It requires a default property (which is basically what traits were anyways) and a lifting of restrictions on the object being implemented. What we solved here is:

1) Default properties merge the namespaces, which is the perhaps the most important part of OOP, that is the class is "one thing". If there is one thing to accomplish it's this. Imagine how annoying OOP would be if you you have to do "sphere.circle.shape.Draw()" because we had TSphere which inherited from TCircle and in turn inherited from TShape.

2) If we can lift the restriction of classes-only by using records/objects then we don't need to write boilerplate to alloc/free classes and our fields are now contagious in memory. Again that would be so annoying if classes made us allocate and free the super class we were inheriting from.

What it leaves desired:

1) Making a dummy interface is annoying boiler plate but not a deal breaker.

2) No fields or properties although you have some solution below which will probably require some additional boiler plate. Class operators would are kind of sad to lose too but not a deal breaker.

I stand by that using some concept of traits/mixins does all the stuff we want with less boiler plate but I'm happy to explore any other ideas.

Of course this does not provide any mechanism to directly add fields, however the compiler could in theory optimize access through property getters/setters if they're accessed through the surrounding class instance instead of the interface.

How does this look?

Also this does not address the point of whether these delegates are able to access functionality of the surrounding class. In my opinion however this can be explicitely modelled by providing the class instance through a constructor or property or whatever.

Indeed but this can be solved by more boiler plate. :) In AfterConstruction you can set references as desired. I had other ideas on this as they related to traits but that wouldn't make sense if we were using an existing type like records or classes.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Ben Grasset: This seems possibly a *little* too similar to the existing Interface type in Object…
On Feb 13, 2021, at 5:09 PM, Ben Grasset via fpc-pascal wrote: This seems possibly a *little* too similar to the existing Interface type in Object Pascal, however, I *would* really like to see some kind of functionality that basically amounts to "has the same capabilities as Interfaces and works on records and objects too, but does NOT require any kind of heap allocation".

Not really, traits/mixins are multiple inheritance alternative.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ben Grasset: This seems possibly a *little* too similar to the existing Interface type in Object…
Ben Grasset wrote 5 lines
This seems possibly a *little* too similar to the existing Interface type in Object Pascal, however, I *would* really like to see some kind of functionality that basically amounts to "has the same capabilities as Interfaces and works on records and objects too, but does NOT require any kind of heap allocation".

My personal idea for this would be to allow for duck typing regarding interfaces: as long as the methods of the class, object or record satisfy those of the interface then it's possible to assign it. In case of objects and records one would mainly use raw interfaces (aka corba ones) instead of reference counted one as otherwise one would need to provide the methods of IInterface as well though one might not be able to implement them in a usable way as stack objects simply don't work that way.

So whether it be this, or just an improvement on the Interfaces we already have, I'd definitely personally be in favor of something that "works like Interfaces except minus the negative performance implications."

There are no significant performance implications of interfaces. They're essentially a virtual method call, something that one is doing all day long with Object Pascal classes.

Regards,
Sven

Reply by email

Michael Van Canneyt
to Sven Barth
Sven Barth wrote 8 lines
> So whether it be this, or just an improvement on the Interfaces we > already have, I'd definitely personally be in favor of something that > "works like Interfaces except minus the negative performance > implications." There are no significant performance implications of interfaces. They're essentially a virtual method call, something that one is doing all day long with Object Pascal classes.

I think the "performance implications" is more referring to memory management.

Interfaces means an extra object per interface, so more objects on the heap with all the extra memory management that implies.

That is why I think that a 'trait' using an object/advanced record has it's advantage: There is only 1 continuous memory block for a class implementing traits.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
On Feb 14, 2021, at 5:30 AM, Michael Van Canneyt via fpc-pascal wrote: Interfaces means an extra object per interface, so more objects on the heap with all the extra memory management that implies.

You mean COM interfaces? I've only ever used CORBA style ones which I thought were just contracts basically. The supports() function returns a pointer to something which I thought was part of the existing VMT table.

That is why I think that a 'trait' using an object/advanced record has it's advantage: There is only 1 continuous memory block for a class implementing traits.

Considering the functionality we actually want, i.e. something like inheritance, you could achieve the trait by simply making an "imports" property. No need to even introduce the extra "trait" type. For example:

  property Test: TTestImpl import FTest;

which is basically the same as:

  property Test: TTestImpl read fTest implements ITest; default;

except it doesn't require an interface. The thing we seem to want here is a default property which works on records/objects so maybe we can just focus on that? The interface has so much boiler plate and I don't really see what we're getting in return.

Regards,
Ryan Joseph

Reply by email

Michael Van Canneyt
to Ryan Joseph
Ryan Joseph wrote 21 lines
> On Feb 14, 2021, at 5:30 AM, Michael Van Canneyt via fpc-pascal wrote: > > Interfaces means an extra object per interface, so more objects on the heap with all the extra memory management that implies. You mean COM interfaces? I've only ever used CORBA style ones which I thought were just contracts basically. The supports() function returns a pointer to something which I thought was part of the existing VMT table. > > That is why I think that a 'trait' using an object/advanced record has it's advantage: There is only 1 continuous memory block for a class implementing > traits. Considering the functionality we actually want, i.e. something like inheritance, you could achieve the trait by simply making an "imports" property. No need to even introduce the extra "trait" type. For example: property Test: TTestImpl import FTest; which is basically the same as: property Test: TTestImpl read fTest implements ITest; default; except it doesn't require an interface. The thing we seem to want here is a default property which works on records/objects so maybe we can just focus on that? The interface has so much boiler plate and I don't really see what we're getting in return.

I didn't propose using an interface; For me the

property Test: TTestImpl read fTest implements TTestIimpl;

Where TTestImpl is an object (tp style) or advanced record (delphi style) is ample.

Michael.

Reply by email

Ryan Joseph
to Michael Van Canneyt
Show quoted text 7 lines
On Feb 14, 2021, at 9:16 AM, Michael Van Canneyt via fpc-pascal wrote: I didn't propose using an interface; For me the property Test: TTestImpl read fTest implements TTestIimpl; Where TTestImpl is an object (tp style) or advanced record (delphi style) is ample.

That was directly like the idea of a "default property" and Sven appears to agree, but there's some debate on how this should be achieved. In fact we already had another long thread on default properties some years ago but it got derailed due to the implication of using default properties with all types.

Maybe what we really want is a default property for struct types which does strict name collision checking?

property Test: TSomeRec read fTest; default;

That's a simple idea which is easy to reason about, doesn't require any new syntax and is easy to implement (more on that if we get that far).

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Michael Van Canneyt: I think the "performance implications" is more referring to memory management. Interfaces…
Michael Van Canneyt wrote 18 lines
On Sun, 14 Feb 2021, Sven Barth via fpc-pascal wrote: >> So whether it be this, or just an improvement on the Interfaces we >> already have, I'd definitely personally be in favor of something >> that "works like Interfaces except minus the negative performance >> implications." > > There are no significant performance implications of interfaces. > They're essentially a virtual method call, something that one is > doing all day long with Object Pascal classes. I think the "performance implications" is more referring to memory management. Interfaces means an extra object per interface, so more objects on the heap with all the extra memory management that implies.

No, interfaces by *themselves* don't mean that. An interface instance is merely a shifted Self pointer that points to the static VMT of which the entries correct the Self pointer again upon the method call. This can be seen in GetInterfaceByEntry in rtl/inc/objpas.inc (the core function dealing with the casting of class to interface).

That is why I think that a 'trait' using an object/advanced record has it's advantage: There is only 1 continuous memory block for a class implementing traits.

For delegation it is true that memory allocation is involved, but let's be fair here: this instance is normally allocated together with its parent instance and thus the performance impact only adds to the already costly instantiation of the parent instance. And if you really need to worry about heap fragmentation then you have other problems in my opinion...

That said, as I wrote, one could in principle allow the use of records and objects for the delegation as well, after all delegation classes don't need to implement the interface either and the compiler needs to create appropriate thunks anyway.

Regards,
Sven

Reply by email

Michael Van Canneyt
to Sven Barth
Sven Barth wrote 21 lines
Am 14.02.2021 um 13:30 schrieb Michael Van Canneyt via fpc-pascal: > > > On Sun, 14 Feb 2021, Sven Barth via fpc-pascal wrote: > >>> So whether it be this, or just an improvement on the Interfaces we >>> already have, I'd definitely personally be in favor of something >>> that "works like Interfaces except minus the negative performance >>> implications." >> >> There are no significant performance implications of interfaces. >> They're essentially a virtual method call, something that one is >> doing all day long with Object Pascal classes. > > I think the "performance implications" is more referring to memory > management. > > Interfaces means an extra object per interface, so more objects on the > heap with all the extra memory management that implies. No, interfaces by *themselves* don't mean that.

I meant in the context of traits and mixins, obviously.

Michael.

Reply by email

Sven Barth
to Ryan Joseph: What I'm trying to solve is the dilemma of OOP where we want to extend a class but the…
Ryan Joseph wrote 8 lines
> On Feb 13, 2021, at 12:38 PM, Sven Barth wrote: > > Right now, Ryan, your suggestion looks like a solution in search of a problem, or a "hey, look at that feature in language X, I so must have that in Pascal as well". Those concepts more likely then not tend to end in problems or should be rejected. So let's first define what we're trying to solve here: What I'm trying to solve is the dilemma of OOP where we want to extend a class but the addition does not fit cleanly into an existing hierarchy. I know we've all had this problem and we were forced to settle on injecting some base class into a hierarchy even though other classes higher up don't need this functionality. Things like class helpers and interface delegation have already been added into the language so clearly there is a need not being addressed. We've all had this problem haven't we? First off lets go summarize the differences/limitations of interface delegation (as it is now) when compared to good, old fashion normal inheritance and OOP: 1) Does not support fields, class operators, properties

Wrong, properties are supported as I showed, though they need to be with getters and setters right now.

And supporting the hoisting of class operators would be *wrong*, because they could only work on the delegated type as it would not know about the parent class.

3) Requires you to define an interface with duplicate methods, so boilerplate code

One can argue whether this is really boilerplate code or just an expression of Pascal's declarativeness (also see below).

Show quoted text 39 lines
> === code begin === > > type > ITest = interface > procedure Test; > end; > > TTestImpl = record > procedure Test; > end; > > TTest = class(TObject, ITest) > private > fTest: TTestImpl; > public > property Test: TTestImpl read fTest implements ITest; default; > end; > > var > t: TTest; > begin > t := TTest.Create; > try > t.Test; // calls t.fTest.Test > finally > t.Free; > end; > end. > > === code end === > > As the compiler needs to generate corresponding thunks anyway whether it needs to do this for a record or object is not really much more effort either. > > Whether the class needs to declare the interface in its interface clause can be argued about. > > But all in all one can see that with a few extensions of existing features one can easily provide a mixin-like, convenient functionality. Great, this is getting closer to inheritance. It requires a default property (which is basically what traits were anyways) and a lifting of restrictions on the object being implemented. What we solved here is: 1) Default properties merge the namespaces, which is the perhaps the most important part of OOP, that is the class is "one thing". If there is one thing to accomplish it's this. Imagine how annoying OOP would be if you you have to do "sphere.circle.shape.Draw()" because we had TSphere which inherited from TCircle and in turn inherited from TShape.

To be fair, that *is* how inheritance works for example if you try to use OOP in C... (though it would be more like "sphere.parent.parent.Draw()")

What it leaves desired: 1) Making a dummy interface is annoying boiler plate but not a deal breaker.

Again, I see this is part of Pascal's declarativeness. Also this way you can make sure that only those methods/properties that you need are hoisted into the parent object allowing for easier reuse of existing classes/records/objects.

Take this for example:

=== code begin ===

type
   TMyRec = record
     procedure DoX;
     procedure DoY;
   end;
   IMyIntf = interface
     procedure DoX;
   end;
   TMyClass = class(TInterfacedObject, IMyIntf)
   private
     fMyRec: TMyRec;
   public
     property MyRec: TMyRec read fMyRec implements IMyIntf; default;
   end;
var
   t: TMyClass;
begin
   t := TMyClass.Create;
   try
     t.DoX;
     // this won't work however
     //t.DoY;
   finally
     t.Free;
   end;
end.

=== code end ===

Also it provides you with the possibility to really only pass on the interface to something that only expects the interface (though with the introduction of duck typed interfaces that wouldn't be that much of a problem either, albeit here the mapping to the interface would be determined at the time of the declaration of the class instead of when the cast happens)

2) No fields or properties although you have some solution below which will probably require some additional boiler plate. Class operators would are kind of sad to lose too but not a deal breaker.

No class operators. As mentioned above they wouldn't work anyway.

Properties *are* supported though they need getters and setters.

I stand by that using some concept of traits/mixins does all the stuff we want with less boiler plate but I'm happy to explore any other ideas.

One can explore further improvements down the road (e.g. fields), but we should provide a solid base first.

> Of course this does not provide any mechanism to directly add fields, however the compiler could in theory optimize access through property getters/setters if they're accessed through the surrounding class instance instead of the interface. How does this look?

Assume the following:

=== code begin ===

type
   TMyRecord = record
   private
     function GetX; inline;
   public
     fX: LongInt;
     property X: LongInt read GetX;
   end;
   IMyIntf = interface
     function GetX;
     property X: LongInt read GetX;
   end;
   TMyClass = class(TInterfacedObject, IMyIntf)
   private
     fMyRecord: TMyRecord;
   public
     property MyRecord: TMyRecord read fMyRecord implements IMyIntf;
default;
   end;
var
   t: TMyClass;
begin
   t := TMyClass.Create;
   try
     // here the compiler knows that X is called through a hoisted
interface property
     // thus it can optimize away the interface related stuff
     // and thus inline the GetX
     Writeln(t.X);
   finally
     t.Free;
   end;
end.
begin
end.

=== code end ===

One could argue that this contains quite some boilerplate code, but one should see this in relation: you're writing IMyIntf and TMyRecord *once* and use it *multiple* times. Thus the only code that you repeat multiple times is the one in TMyClass and here an IDE like Lazarus could help you.

> Also this does not address the point of whether these delegates are able to access functionality of the surrounding class. In my opinion however this can be explicitely modelled by providing the class instance through a constructor or property or whatever. Indeed but this can be solved by more boiler plate. :) In AfterConstruction you can set references as desired. I had other ideas on this as they related to traits but that wouldn't make sense if we were using an existing type like records or classes.

That is what I meant by explicitely modeling, though it doesn't matter if you do it inside the parent classes' constructor or its AfterConstruction. You simply need to make sure that your delegator is set up correctly so that it can do its work.

Regards,
Sven

Reply by email

Martin Frb
to Sven Barth
Sven Barth wrote 8 lines
>> >>    TTest = class(TObject, ITest) >>    private >>      fTest: TTestImpl; >>    public >>      property Test: TTestImpl read fTest implements ITest; default; >>    end;

Well there is a difference there.

Interfaces are essentially inheritance. One can access TTest via "TTest as ITest".

A trait (in any of the proposed forms) would not allow that. (Hence a trait should probably also not be it the  TFoo=class(...) part).

So that makes 2 differences for trait vs interface
- The result of the composition can not be cast to either of the parts making it up
  That is a functional difference
- Traits remove the need to declare all methods twice (once in the interface, once in the class (or whatever implementing struct))   That can be considered syntactic sugar (though it might have a quite impacting effect)

Further, IIRC it was said that:
For interfaces the syntax is already fully available. But some features are not yet supported by fpc.
Though Delphi has them, so the might come anyway.
However for traits as "default property" the syntax also already exists.

Reply by email

Ryan Joseph
to Sven Barth: Wrong, properties are supported as I showed, though they need to be with getters and…
On Feb 14, 2021, at 10:20 AM, Sven Barth via fpc-pascal wrote: To be fair, that *is* how inheritance works for example if you try to use OOP in C... (though it would be more like "sphere.parent.parent.Draw()")

and that was bad. :) in PHP you're forced to use this-> instead of implicit self and even that is annoying.

Show quoted text 6 lines
> What it leaves desired: > > 1) Making a dummy interface is annoying boiler plate but not a deal breaker. Again, I see this is part of Pascal's declarativeness. Also this way you can make sure that only those methods/properties that you need are hoisted into the parent object allowing for easier reuse of existing classes/records/objects.

That is certainly true. With the idea of "default record properties"/traits the visibility sections like public would have to be used to define the exportable members. The "declarativeness" is also boiler plate but I do see your point. Not a deal breaker though but it may get tedious if you used this feature often.

> I stand by that using some concept of traits/mixins does all the stuff we want with less boiler plate but I'm happy to explore any other ideas. One can explore further improvements down the road (e.g. fields), but we should provide a solid base first.

Yes, you're right properties do work but require the getter/settings methods.

Another question, what does overriding mean in this context of interface delegation? With "default implements" it would be possible to add a method with the same name, which should be an error but it opens a possibility to provide a new method implementation.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Sven Barth: No, interfaces by *themselves* don't mean that. An interface instance is merely a shifted…
On Feb 14, 2021, at 9:55 AM, Sven Barth via fpc-pascal wrote: No, interfaces by *themselves* don't mean that. An interface instance is merely a shifted Self pointer that points to the static VMT of which the entries correct the Self pointer again upon the method call. This can be seen in GetInterfaceByEntry in rtl/inc/objpas.inc (the core function dealing with the casting of class to interface).

Remind me again, what happens when you cast a class to an interface? It's a runtime check with GetInterfaceByEntry?

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph
Ryan Joseph wrote 5 lines
> On Feb 14, 2021, at 9:55 AM, Sven Barth via fpc-pascal wrote: > > No, interfaces by *themselves* don't mean that. An interface instance is merely a shifted Self pointer that points to the static VMT of which the entries correct the Self pointer again upon the method call. This can be seen in GetInterfaceByEntry in rtl/inc/objpas.inc (the core function dealing with the casting of class to interface). Remind me again, what happens when you cast a class to an interface? It's a runtime check with GetInterfaceByEntry?

It's a runtime conversion, but normally you don't do the conversion for every method call, but instead cache the interface (or work solely with the interface), thus the performance impact through the conversion amortizes (and again, there's so memory allocation by the conversion itself, of course if the interface getter in case of a interface delegation does a lazy initialization that's different, but the conversion itself does not require an allocation).

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Feb 14, 2021, at 11:14 AM, Sven Barth via fpc-pascal wrote: It's a runtime conversion, but normally you don't do the conversion for every method call, but instead cache the interface (or work solely with the interface), thus the performance impact through the conversion amortizes (and again, there's so memory allocation by the conversion itself, of course if the interface getter in case of a interface delegation does a lazy initialization that's different, but the conversion itself does not require an allocation).

For default properties no conversion needs to take place even because the compiler can simply find the correct method and return a modified self which points to the struct, like "self.fMyRec" in the example below. In this case the interface doesn't really do anything does it? I think the VMT table is modified and you should probably use CORBA interface unless you want reference counting and extra baggage when you instantiate the class (I assume). If we use records then the memory should be unified (which is good) and with CORBA interfaces no significant baggage during instantiation. Correct me where I'm wrong please.

=====================

type
  TMyRec = record
    procedure DoX;
  end;
  IMyIntf = interface
    procedure DoX;
  end;
  TMyClass = class(TInterfacedObject, IMyIntf)
  private
    fMyRec: TMyRec;
  public
    property MyRec: TMyRec read fMyRec implements IMyIntf; default;
  end;

Regards,
Ryan Joseph

Reply by email

Marco van de Voort
to Michael Van Canneyt: I think the "performance implications" is more referring to memory management. Interfaces…

Op 2021-02-14 om 13:30 schreef Michael Van Canneyt via fpc-pascal:

Show quoted text 5 lines
I think the "performance implications" is more referring to memory management. Interfaces means an extra object per interface, so more objects on the heap with all the extra memory management that implies.

(not interfaces do, as they are just a contract, but delegation of interfaces does)

Reply by email

Marco van de Voort
to Michael Van Canneyt: I didn't propose using an interface; For me the property Test: TTestImpl read fTest…

Op 2021-02-14 om 17:16 schreef Michael Van Canneyt via fpc-pascal:

Show quoted text 13 lines
> except it doesn't require an interface. The thing we seem to want > here is a default property which works on records/objects so maybe we > can just focus on that? The interface has so much boiler plate and I > don't really see what we're getting in return. I didn't propose using an interface; For me the property Test: TTestImpl read fTest implements TTestIimpl; Where TTestImpl is an object (tp style) or advanced record (delphi style) is ample.

You mean in both cases this will lead to a deep copy ? :-)

Reply by email

Sven Barth
to Ryan Joseph: and that was bad. :) in PHP you're forced to use this-> instead of implicit self and even…
Ryan Joseph wrote 6 lines
>> What it leaves desired: >> >> 1) Making a dummy interface is annoying boiler plate but not a deal breaker. > Again, I see this is part of Pascal's declarativeness. Also this way you can make sure that only those methods/properties that you need are hoisted into the parent object allowing for easier reuse of existing classes/records/objects. That is certainly true. With the idea of "default record properties"/traits the visibility sections like public would have to be used to define the exportable members. The "declarativeness" is also boiler plate but I do see your point. Not a deal breaker though but it may get tedious if you used this feature often.

One can always think about relaxations/improvements later on. As said before: let's get the foundation down right, first.

Another question, what does overriding mean in this context of interface delegation? With "default implements" it would be possible to add a method with the same name, which should be an error but it opens a possibility to provide a new method implementation.

Same names should be rejected. Providing a new implementation can be controlled using the interface alias:

=== code begin ===

type
   TMyClass = class(TInterfacedObject, IMyIntf)
   private
     fMyRecord: TMyRecord;
   public
     procedure IMyIntf.Test2 = MyTest2;
     // this will hoist all methods except Test2 from TMyRecord and
MyTest2 as Test2 from the class itself
     property MyRecord: TMyRecord read fMyRecord implements IMyIntf;
default;
     propcedure MyTest2;
   end;

=== code end ===

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
Show quoted text 16 lines
On Feb 14, 2021, at 11:43 PM, Sven Barth via fpc-pascal wrote: Same names should be rejected. Providing a new implementation can be controlled using the interface alias: === code begin === type TMyClass = class(TInterfacedObject, IMyIntf) private fMyRecord: TMyRecord; public procedure IMyIntf.Test2 = MyTest2; // this will hoist all methods except Test2 from TMyRecord and MyTest2 as Test2 from the class itself property MyRecord: TMyRecord read fMyRecord implements IMyIntf; default; propcedure MyTest2; end;

So "procedure IMyIntf.Test2 = MyTest2;" basically tells the compiler to ignore the duplicate name resections and then when it encounters the MyTest2 there is no error?

A duplicate name in this context is basically just method hiding (like in normal OOP) so you could argue it should be allowed. If the property came before then the interface would hide the local declaration. Not too different from how normal inheritance works really.

Regards,
Ryan Joseph

Reply by email

Marcos Douglas B. Santos
to Ryan Joseph
PM Ryan Joseph wrote 22 lines
> On Feb 14, 2021, at 11:43 PM, Sven Barth via fpc-pascal wrote: > > Same names should be rejected. Providing a new implementation can be controlled using the interface alias: > > === code begin === > > type > TMyClass = class(TInterfacedObject, IMyIntf) > private > fMyRecord: TMyRecord; > public > procedure IMyIntf.Test2 = MyTest2; > // this will hoist all methods except Test2 from TMyRecord and MyTest2 as Test2 from the class itself > property MyRecord: TMyRecord read fMyRecord implements IMyIntf; default; > propcedure MyTest2; > end; So "procedure IMyIntf.Test2 = MyTest2;" basically tells the compiler to ignore the duplicate name resections and then when it encounters the MyTest2 there is no error? A duplicate name in this context is basically just method hiding (like in normal OOP) so you could argue it should be allowed. If the property came before then the interface would hide the local declaration. Not too different from how normal inheritance works really.

As I understand, this is not a method hiding, but just to tell the compiler which method to implement the interface—the interface could have method names which are very very common to use and this syntax allows us to "rename" those methods without changing the original names that the class might have.

Marcos Douglas

Reply by email

Ryan Joseph
to Marcos Douglas B. Santos
Show quoted text 7 lines
On Feb 15, 2021, at 9:08 AM, Marcos Douglas B. Santos via fpc-pascal wrote: As I understand, this is not a method hiding, but just to tell the compiler which method to implement the interface—the interface could have method names which are very very common to use and this syntax allows us to "rename" those methods without changing the original names that the class might have.

yeah you may be right, that the interface object itself won't be hookup correctly if this doesn't happen. Seems strange you have 2 declarations for the same method though.

  procedure IMyIntf.Test2 = MyTest2;
  propcedure MyTest2;

instead of something like:

  propcedure MyTest2 override  IMyIntf.Test2;

Regards,
Ryan Joseph

Reply by email

Marco van de Voort
to Ryan Joseph

Op 2021-02-15 om 18:08 schreef Ryan Joseph via fpc-pascal:

Show quoted text 15 lines
> As I understand, this is not a method hiding, but just to tell the > compiler which method to implement the interface—the interface could > have method names which are very very common to use and this syntax > allows us to "rename" those methods without changing the original > names that the class might have. yeah you may be right, that the interface object itself won't be hookup correctly if this doesn't happen. Seems strange you have 2 declarations for the same method though. procedure IMyIntf.Test2 = MyTest2; propcedure MyTest2; instead of something like: propcedure MyTest2 override IMyIntf.Test2;

The first syntax allows to for methods to be declared in the ancestor too.

Reply by email

Sven Barth
to Ryan Joseph: So "procedure IMyIntf.Test2 = MyTest2;" basically tells the compiler to ignore the…
Ryan Joseph wrote 18 lines
> On Feb 14, 2021, at 11:43 PM, Sven Barth via fpc-pascal wrote: > > Same names should be rejected. Providing a new implementation can be controlled using the interface alias: > > === code begin === > > type > TMyClass = class(TInterfacedObject, IMyIntf) > private > fMyRecord: TMyRecord; > public > procedure IMyIntf.Test2 = MyTest2; > // this will hoist all methods except Test2 from TMyRecord and MyTest2 as Test2 from the class itself > property MyRecord: TMyRecord read fMyRecord implements IMyIntf; default; > propcedure MyTest2; > end; So "procedure IMyIntf.Test2 = MyTest2;" basically tells the compiler to ignore the duplicate name resections and then when it encounters the MyTest2 there is no error?

No, it tells the compiler to fullfill the IMyIntf.Test2 requirement by using TMyClass.MyTest2 instead of TMyRecord.Test2. This way you can control which methods are from the delegate and which might be specific to the container class. It also allows to differentiate if you have two interfaces with the same method:

=== code begin ===

type
   IMyIntf1 = interface
     procedure Test;
   end;
   IMyIntf2 = interface
     procedure Test;
   end;
   TMyClass = class(TInterfacedObject, IMyIntf1, IMyIntf2)
   public
     procedure IMyIntf1.Test = Foobar;
     procedure Test; // for IMyIntf2
     procedure Foobar; // for IMyIntf1
   end;

=== code end ===

A duplicate name in this context is basically just method hiding (like in normal OOP) so you could argue it should be allowed. If the property came before then the interface would hide the local declaration. Not too different from how normal inheritance works really.

It should only hide if the method in question is from a parent class. For the declarations in the same class there should be errors, cause the implementor should know better than to introduce potentially ambigious situations.

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: yeah you may be right, that the interface object itself won't be hookup correctly if this…
Ryan Joseph wrote 17 lines
> On Feb 15, 2021, at 9:08 AM, Marcos Douglas B. Santos via fpc-pascal wrote: > > As I understand, this is not a method hiding, but just to tell the > compiler which method to implement the interface—the interface could > have method names which are very very common to use and this syntax > allows us to "rename" those methods without changing the original > names that the class might have. yeah you may be right, that the interface object itself won't be hookup correctly if this doesn't happen. Seems strange you have 2 declarations for the same method though. procedure IMyIntf.Test2 = MyTest2; propcedure MyTest2; instead of something like: propcedure MyTest2 override IMyIntf.Test2;

Because the first one is much more powerful. As Marco said, you can reference a method in the parent class. Also you can redirect different interface methods to the same method:

=== code begin ===

type
   IMyIntf1 = interface
     procedure Foo;
   end;
   IMyIntf2 = interface
     procedure Bar;
   end;
   TMyClass = class(TInterfacedObject, IMyIntf1, IMyIntf2)
   public
     procedure IMyIntf1.Foo = Foobar;
     procedure IMyIntf2.Bar = Foobar;
     procedure Foobar;
   end;

=== code end ===

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: For default properties no conversion needs to take place even because the compiler can…
Ryan Joseph wrote 5 lines
> On Feb 14, 2021, at 11:14 AM, Sven Barth via fpc-pascal wrote: > > It's a runtime conversion, but normally you don't do the conversion for every method call, but instead cache the interface (or work solely with the interface), thus the performance impact through the conversion amortizes (and again, there's so memory allocation by the conversion itself, of course if the interface getter in case of a interface delegation does a lazy initialization that's different, but the conversion itself does not require an allocation). For default properties no conversion needs to take place even because the compiler can simply find the correct method and return a modified self which points to the struct, like "self.fMyRec" in the example below. In this case the interface doesn't really do anything does it? I think the VMT table is modified and you should probably use CORBA interface unless you want reference counting and extra baggage when you instantiate the class (I assume). If we use records then the memory should be unified (which is good) and with CORBA interfaces no significant baggage during instantiation. Correct me where I'm wrong please.

Again, the point of the interface would be to control which methods and properties of the delegated  type would be available. Also it would allow for seamless integration with the RTTI as it's possible to query the implemented interface of a class and to retrieve a reference to it, thus it would be possible to access this at runtime as well.

The compiler will generate a VMT for the interface with method thunks that adjust the Self pointer so that it works correctly. The GetInterfaceByEntry function uses this static data to generate an adjusted Self value that callers can use.

Also for the difference between COM and raw interfaces the only real difference is that a call to _AddRef will be inserted. But I agree that it should also work with raw interfaces (if it doesn't already).

Regards,
Sven

Reply by email

Benito van der Zander
to Sven Barth: My personal idea for this would be to allow for duck typing regarding interfaces: as long…
There are no significant performance implications of interfaces. They're essentially a virtual method call, something that one is doing all day long with Object Pascal classes.

Interfaces are extremely slow. Virtual method calls are also slow. I have been using interfaces for reference counting, and have been thinking to replace it all with managed records because they are so slow (unfortunately records can also be slow because fpc does not always keep them in registers even if they are pointer sized)

There we have:

* slower creation of the class, because each implemented interface adds another VMT reference to the class which needs to be initialized.

* slower method calling because of the wrapper function

* extremely slow casting an interface to the class. Never cast an interface, better add a method to the interface returning a class reference

* slow reference counting. Especially if it is thread safe and exception safe with the implicit exception block

Sven Barth wrote 30 lines
Am 14.02.2021 um 01:09 schrieb Ben Grasset via fpc-pascal: > This seems possibly a *little* too similar to the existing Interface > type in Object Pascal, however, I *would* really like to see some > kind of functionality that basically amounts to "has the same > capabilities as Interfaces and works on records and objects too, but > does NOT require any kind of heap allocation". My personal idea for this would be to allow for duck typing regarding interfaces: as long as the methods of the class, object or record satisfy those of the interface then it's possible to assign it. In case of objects and records one would mainly use raw interfaces (aka corba ones) instead of reference counted one as otherwise one would need to provide the methods of IInterface as well though one might not be able to implement them in a usable way as stack objects simply don't work that way. > So whether it be this, or just an improvement on the Interfaces we > already have, I'd definitely personally be in favor of something that > "works like Interfaces except minus the negative performance > implications." There are no significant performance implications of interfaces. They're essentially a virtual method call, something that one is doing all day long with Object Pascal classes. Regards, Sven _______________________________________________ fpc-pascal maillist  - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Sven Barth: Again, the point of the interface would be to control which methods and properties of the…
Show quoted text 5 lines
On Feb 15, 2021, at 11:41 PM, Sven Barth via fpc-pascal wrote: Again, the point of the interface would be to control which methods and properties of the delegated type would be available. Also it would allow for seamless integration with the RTTI as it's possible to query the implemented interface of a class and to retrieve a reference to it, thus it would be possible to access this at runtime as well. The compiler will generate a VMT for the interface with method thunks that adjust the Self pointer so that it works correctly. The GetInterfaceByEntry function uses this static data to generate an adjusted Self value that callers can use.

As far as the "default implements property" is concerned we don't actually need, or even want to utilize the interfaces themselves. The idea of leveraging the interface syntax for exporting method names is one thing but if we actually have to use the interfaces internally then we open a whole can of worms in terms of performance. Class instantiation may be taking on some significant baggage now also as the compiler sets up these interface VMT tables, even if the user never needs or wants them.

For example this code should be a compile time indirection instead of a hidden interface cast, right? The method forwarding syntax which works by modifying the VMT can hopefully be done at compile time for the default property also.

type
  ICircle = interface
    procedure Draw;
  end;
type
  TCircle = record
    procedure Draw;
  end;
type
  TMyShape = class(ICircle)
    private
      FCircle: TCircle;
    public
      property Circle: TCircle read FCircle implements ICircle; default;
  end;
var
  shape: TMyShape;
begin
  shape := TMyShape.Create;
  { calls shape.FCircle.Draw instead of ICircle(shape).Draw }
  shape.Draw;
end.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Benito van der Zander: Interfaces are extremely slow. Virtual method calls are also slow. I have been using…
On Feb 16, 2021, at 6:51 AM, Benito van der Zander via fpc-pascal wrote: Interfaces are extremely slow. Virtual method calls are also slow. I have been using interfaces for reference counting, and have been thinking to replace it all with managed records because they are so slow (unfortunately records can also be slow because fpc does not always keep them in registers even if they are pointer sized)

It's a real concern indeed. What we're proposing with a default property doesn't need to have these impacts but the class instantiation is going to be affected I think.

There we have: * slower creation of the class, because each implemented interface adds another VMT reference to the class which needs to be initialized.

How bad is this? We need to make a test case we can profile. For example if you have a game that creates 1000 classes 60 frames per second. If adding interfaces to the classes hurts this process we may have a deal breaker.

* slower method calling because of the wrapper function

You mean for fields? We can inline those I believe but it's not fun boiler plate for sure.

* extremely slow casting an interface to the class. Never cast an interface, better add a method to the interface returning a class reference

We can avoid casting using the default property but it will still be possible to cast the class if you want to.

* slow reference counting. Especially if it is thread safe and exception safe with the implicit exception block

It's a whole other topic but FPC needs opt-in ARC at the language level for classes. Interfaces are not only slow but then you need to pass around a reference to the interface instead of the class you actually care about. It makes no sense to me whatsoever.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph: As far as the "default implements property" is concerned we don't actually need, or even…

Ryan Joseph via fpc-pascal schrieb am
Di., 16. Feb. 2021, 19:22:

Show quoted text 29 lines
> On Feb 15, 2021, at 11:41 PM, Sven Barth via fpc-pascal < > wrote: > > Again, the point of the interface would be to control which methods and properties of the delegated type would be available. Also it would allow for seamless integration with the RTTI as it's possible to query the implemented interface of a class and to retrieve a reference to it, thus it would be possible to access this at runtime as well. > > The compiler will generate a VMT for the interface with method thunks that adjust the Self pointer so that it works correctly. The GetInterfaceByEntry function uses this static data to generate an adjusted Self value that callers can use. As far as the "default implements property" is concerned we don't actually need, or even want to utilize the interfaces themselves. The idea of leveraging the interface syntax for exporting method names is one thing but if we actually have to use the interfaces internally then we open a whole can of worms in terms of performance. Class instantiation may be taking on some significant baggage now also as the compiler sets up these interface VMT tables, even if the user never needs or wants them. For example this code should be a compile time indirection instead of a hidden interface cast, right? The method forwarding syntax which works by modifying the VMT can hopefully be done at compile time for the default property also.

I wasn't saying anything that contradicts this, only that the original mechanism of the delegation will be available even with the default modifier and this mechanism will in fact be necessary to access them through RTTI.

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: It's a real concern indeed. What we're proposing with a default property doesn't need to…

Ryan Joseph via fpc-pascal schrieb am
Di., 16. Feb. 2021, 19:21:

Show quoted text 11 lines
> > There we have: > > * slower creation of the class, because each implemented interface adds another VMT reference to the class which needs to be initialized. How bad is this? We need to make a test case we can profile. For example if you have a game that creates 1000 classes 60 frames per second. If adding interfaces to the classes hurts this process we may have a deal breaker.

If you need to create 1000 class instances each frame then you have a flaw in your logic in my opinion.

Show quoted text 8 lines
> * slow reference counting. Especially if it is thread safe and exception safe with the implicit exception block It's a whole other topic but FPC needs opt-in ARC at the language level for classes. Interfaces are not only slow but then you need to pass around a reference to the interface instead of the class you actually care about. It makes no sense to me whatsoever.

Interfaces are not slow because they are are interfaces! When you call a interface method it has the same costs as a virtual method call! And the cost for reference counting that interfaces have right now would be there for ARC as well.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Di., 16. Feb. 2021, 19:22: I wasn't saying…
On Feb 16, 2021, at 11:44 AM, Sven Barth wrote: I wasn't saying anything that contradicts this, only that the original mechanism of the delegation will be available even with the default modifier and this mechanism will in fact be necessary to access them through RTTI.

Can you give an example of this syntax? I get errors like Interface "ICircle" cannot be delegated by "TMyShape", it already has method resolutions.

It's a strange syntax compared to anything else we allow in classes and the boiler plate is really adding up now. "procedure Draw" is going to be in 4 lines now!

It would be nicer if they made this into one line so we don't have yet another set of duplicate method signatures, i.e.:

  procedure Draw as ICircle.Draw;

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Di., 16. Feb. 2021, 19:21: If you need to create…
On Feb 16, 2021, at 11:48 AM, Sven Barth wrote: If you need to create 1000 class instances each frame then you have a flaw in your logic in my opinion.

You can reuse classes and reset fields but getting a new piece of memory could be faster. These things do happen and adding on class allocation performance penalties could be a problem. I need to profile first to know what we're talking about here.

Regards,
Ryan Joseph

Reply by email

Ralf Quint
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Di., 16. Feb. 2021, 19:21: If you need to create…
Sven Barth wrote 19 lines
Ryan Joseph via fpc-pascal < > schrieb am Di., 16. Feb. 2021, 19:21: > > There we have: > > * slower creation of the class, because each implemented interface adds another VMT reference to the class which needs to be initialized. How bad is this? We need to make a test case we can profile. For example if you have a game that creates 1000 classes 60 frames per second. If adding interfaces to the classes hurts this process we may have a deal breaker. If you need to create 1000 class instances each frame then you have a flaw in your logic in my opinion.

Yup, that would be a deal breaker right from the get go...

Ralf

This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Marco van de Voort
to Ryan Joseph: It's a real concern indeed. What we're proposing with a default property doesn't need to…

Op 2021-02-16 om 19:21 schreef Ryan Joseph via fpc-pascal:

Interfaces are extremely slow. Virtual method calls are also slow. I have been using interfaces for reference counting, and have been thinking to replace it all with managed records because they are so slow (unfortunately records can also be slow because fpc does not always keep them in registers even if they are pointer sized)

And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no

Reply by email

Ryan Joseph
to Marco van de Voort
On Feb 16, 2021, at 1:27 PM, Marco van de Voort via fpc-pascal wrote: And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no

deep copying? You mean from ref counted types like "array of" and ansistring?

Regards,
Ryan Joseph

Reply by email

Marco van de Voort
to Ryan Joseph

Op 2021-02-16 om 21:33 schreef Ryan Joseph via fpc-pascal:

Show quoted text 5 lines
> On Feb 16, 2021, at 1:27 PM, Marco van de Voort via fpc-pascal wrote: > > And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no deep copying? You mean from ref counted types like "array of" and ansistring?

No, use of TP objects and records in e.g. properties in not carefully crafted code. They are a lot less userfriendly.

Reply by email

Sven Barth
to Ryan Joseph: Can you give an example of this syntax? I get errors like Interface "ICircle" cannot be…
Ryan Joseph wrote 6 lines
> On Feb 16, 2021, at 11:44 AM, Sven Barth wrote: > > I wasn't saying anything that contradicts this, only that the original mechanism of the delegation will be available even with the default modifier and this mechanism will in fact be necessary to access them through RTTI. > Can you give an example of this syntax? I get errors like Interface "ICircle" cannot be delegated by "TMyShape", it already has method resolutions.

FPC currently does not yet support class types not to mention records and objects which is what I had mentioned earlier already.

Show quoted text 5 lines
It's a strange syntax compared to anything else we allow in classes and the boiler plate is really adding up now. "procedure Draw" is going to be in 4 lines now! It would be nicer if they made this into one line so we don't have yet another set of duplicate method signatures, i.e.: procedure Draw as ICircle.Draw;

I can't shake the feeling that all you complain about is writing even a little bit too much. Pascal is *not* about using as less code as possible, but to be as correct as possible and the syntax of interface delegation allows exactly that.

Regards,
Sven

Reply by email

Benito van der Zander
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Di., 16. Feb. 2021, 19:21: If you need to create…

Hi,

If you need to create 1000 class instances each frame then you have a flaw in your logic in my opinion.

I have more like a million class instances

For my XPath stuff, and every returned value is put in a variant-like class. Selecting all nodes on an GB large XML, could even create almost a billion class instances

I once did a benchmark. It is 10% faster to reuse the class instances rather than recreating them, my commit log says. And I only implemented one interface (actually, two interfaces, since IUnknown always gets pulled in, too. )

Interfaces are not slow because they are are interfaces! When you call a interface method it has the same costs as a virtual method call! And the cost for reference counting that interfaces have right now would be there for ARC as well.

But it is not calling the method, it is calling the wrapper function

That is a additional second method call/jump for every interface method call.

E.g. _AddRef on TInterfacedObject as an interface, does not call TInterfacedObject._AddRef, it calls this:

WRPR_$SYSTEM_$$_TINTERFACEDOBJECT_$_IUNKNOWN_$_1_$_SYSTEM$_$TINTERFACEDOBJECT_$__$$__ADDREF$$LONGINT

0000000000424600 4883ef10                 sub    $0x10,%rdi 0000000000424604 e997f8feff               jmpq   0x413ea0 <SYSTEM$_$TINTERFACEDOBJECT_$__$$__ADDREF$$LONGINT>

Bye,
Benito

Sven Barth wrote 42 lines
Ryan Joseph via fpc-pascal < > schrieb am Di., 16. Feb. 2021, 19:21: > > There we have: > > * slower creation of the class, because each implemented interface adds another VMT reference to the class which needs to be initialized. How bad is this? We need to make a test case we can profile. For example if you have a game that creates 1000 classes 60 frames per second. If adding interfaces to the classes hurts this process we may have a deal breaker. If you need to create 1000 class instances each frame then you have a flaw in your logic in my opinion. > * slow reference counting. Especially if it is thread safe and exception safe with the implicit exception block It's a whole other topic but FPC needs opt-in ARC at the language level for classes. Interfaces are not only slow but then you need to pass around a reference to the interface instead of the class you actually care about. It makes no sense to me whatsoever. Interfaces are not slow because they are are interfaces! When you call a interface method it has the same costs as a virtual method call! And the cost for reference counting that interfaces have right now would be there for ARC as well. Regards, Sven _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Sven Barth
to Benito van der Zander

Benito van der Zander via fpc-pascal
schrieb am Di., 16. Feb. 2021, 23:35:

Show quoted text 20 lines
Interfaces are not slow because they are are interfaces! When you call a interface method it has the same costs as a virtual method call! And the cost for reference counting that interfaces have right now would be there for ARC as well. But it is not calling the method, it is calling the wrapper function That is a additional second method call/jump for every interface method call. E.g. _AddRef on TInterfacedObject as an interface, does not call TInterfacedObject._AddRef, it calls this: WRPR_$SYSTEM_$$_TINTERFACEDOBJECT_$_IUNKNOWN_$_1_$_SYSTEM$_$TINTERFACEDOBJECT_$__$$__ADDREF$$LONGINT 0000000000424600 4883ef10 sub $0x10,%rdi 0000000000424604 e997f8feff jmpq 0x413ea0 <SYSTEM$_$TINTERFACEDOBJECT_$__$$__ADDREF$$LONGINT>

It's an *unconditional* jump. The branch prediction of the CPU should handle this without much penalty.

Regards,
Sven

Reply by email

Benito van der Zander
to Marco van de Voort: Op 2021-02-16 om 21:33 schreef Ryan Joseph via fpc-pascal: No, use of TP objects and…

Hi,

And there often is a lot of unintentional deep copying. This is also why a property returning a record is fairly useless except for extremely small records like TPoint (and even that is not optimal no

But a managed record to replace an interface, would only contain a single pointer/class ref. That can be copied fast

Bye,
Benito

Marco van de Voort wrote 19 lines
Op 2021-02-16 om 21:33 schreef Ryan Joseph via fpc-pascal: > >> On Feb 16, 2021, at 1:27 PM, Marco van de Voort via fpc-pascal >> wrote: >> >> And there often is a lot of unintentional deep copying. This is also >> why a property returning a record is fairly useless except for >> extremely small records like TPoint (and even that is not optimal no > deep copying? You mean from ref counted types like "array of" and > ansistring? No, use of TP objects and records in e.g. properties in not carefully crafted code. They are a lot less userfriendly. _______________________________________________ fpc-pascal maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Sven Barth: FPC currently does not yet support class types not to mention records and objects which…
On Feb 16, 2021, at 2:43 PM, Sven Barth via fpc-pascal wrote: FPC currently does not yet support class types not to mention records and objects which is what I had mentioned earlier already.

Yes I see this on https://freepascal.org/docs-html/ref/refse47.html. Why isn't this supported? I guess as part of this proposal we need to implement this feature as well? I'm not familiar with this area of the language so I'm not sure what this entails.

Show quoted text 8 lines
> It's a strange syntax compared to anything else we allow in classes and the boiler plate is really adding up now. "procedure Draw" is going to be in 4 lines now! > > It would be nicer if they made this into one line so we don't have yet another set of duplicate method signatures, i.e.: > > procedure Draw as ICircle.Draw; I can't shake the feeling that all you complain about is writing even a little bit too much. Pascal is *not* about using as less code as possible, but to be as correct as possible and the syntax of interface delegation allows exactly that.

Being too verbose is clunky in my opinion. That method resolution syntax is just kind of an odd addition for classes to my eyes. "procedure Type.Name = Name;" doesn't have any precedence inside classes/records so it stands out.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Benito van der Zander: I have more like a million class instances For my XPath stuff, and every returned value…
On Feb 16, 2021, at 3:35 PM, Benito van der Zander via fpc-pascal wrote: But it is not calling the method, it is calling the wrapper function

what you say wrapper function do you mean the ref counting functions when you pass around the interface?

I guess for ref counting you're supposed to cast the class to the interface (which is an expensive runtime lookup) and then pass that thing around? That seems like a crazy way to get ref counting plus you need to cast the interface back to the class if you want to call other methods? I still don't get this pattern. ARC should be handled by the compiler and be on the class itself, not some additional object you need to create and then store.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph: Yes I see this on https://freepascal.org/docs-html/ref/refse47.html. Why isn't this…

Ryan Joseph via fpc-pascal schrieb am
Mi., 17. Feb. 2021, 02:15:

Show quoted text 13 lines
> On Feb 16, 2021, at 2:43 PM, Sven Barth via fpc-pascal < > wrote: > > FPC currently does not yet support class types not to mention records and objects which is what I had mentioned earlier already. Yes I see this on https://freepascal.org/docs-html/ref/refse47.html. Why isn't this supported? I guess as part of this proposal we need to implement this feature as well? I'm not familiar with this area of the language so I'm not sure what this entails.

Simply because no one has come around to implement it yet. The class type case is more complicated than the interface case. (Same would be true for record and objects)
So, yes, we'll need to implement this first which would finally bring FPC up to Delphi compatibility regarding interface delegation.

Show quoted text 20 lines
> >> It's a strange syntax compared to anything else we allow in classes and the boiler plate is really adding up now. "procedure Draw" is going to be in 4 lines now! >> >> It would be nicer if they made this into one line so we don't have yet another set of duplicate method signatures, i.e.: >> >> procedure Draw as ICircle.Draw; > > I can't shake the feeling that all you complain about is writing even a little bit too much. Pascal is *not* about using as less code as possible, but to be as correct as possible and the syntax of interface delegation allows exactly that. Being too verbose is clunky in my opinion. That method resolution syntax is just kind of an odd addition for classes to my eyes. "procedure Type.Name = Name;" doesn't have any precedence inside classes/records so it stands out.

Again, the syntax is much more powerful than what you suggested. Also it's the existing syntax and not something new. Not to mention that it only needs to be used if you need to implement a method yourself. Iff(!) we find out down the line once we have the basic functionality working, that people are struggling with this, we can still think about improving something, but for now I'd prefer to add as less new syntaxes as possible (especially if a syntax that does provide this exact functionality *does* exist already, no matter if it's too verbose for some).

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: what you say wrapper function do you mean the ref counting functions when you pass around…

Ryan Joseph via fpc-pascal schrieb am
Mi., 17. Feb. 2021, 02:21:

Show quoted text 10 lines
> On Feb 16, 2021, at 3:35 PM, Benito van der Zander via fpc-pascal < > wrote: > > But it is not calling the method, it is calling the wrapper function what you say wrapper function do you mean the ref counting functions when you pass around the interface?

No. With wrapper function he means the thunks that are responsible for adjusting the instance pointer of the interface to the instance pointer of the class before jumping to the method of the class.

I guess for ref counting you're supposed to cast the class to the interface

Show quoted text 6 lines
(which is an expensive runtime lookup) and then pass that thing around? That seems like a crazy way to get ref counting plus you need to cast the interface back to the class if you want to call other methods? I still don't get this pattern. ARC should be handled by the compiler and be on the class itself, not some additional object you need to create and then store.

I have the feeling that you don't really know how interfaces work. ARC would have the exact same penalty as the reference counting of interfaces, because that is essentially what the compiler would use: call some function that increases/decreases the reference count of the instance. That is *exactly* what is done for interfaces.

Also the interface is not created. It's part of the class instance. You simply have a pointer sized value that points to that. In the ideal situation of *only* working with the interface you can simply forget the class instance, cause the class will be freed once the reference count reached 0. Thus there will be *no* additional storage involved.

Regards,
Sven

Reply by email

Marco van de Voort
to Benito van der Zander: But a managed record to replace an interface, would only contain a single pointer/class…

Op 2021-02-17 om 00:02 schreef Benito van der Zander via fpc-pascal:

Show quoted text 7 lines
> > And there often is a lot of unintentional deep copying. This is also > why a property returning a record is fairly useless except for > extremely small records like TPoint (and even that is not optimal no But a managed record to replace an interface, would only contain a single pointer/class ref. That can be copied fast

(1) A record is not a pointer. So that would require some implicit referencing in the property

(2) If it was managed, it would be an allocation, so I don't understand this.

Reply by email

Ryan Joseph
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Mi., 17. Feb. 2021, 02:21: No. With wrapper…
On Feb 17, 2021, at 6:17 AM, Sven Barth wrote: No. With wrapper function he means the thunks that are responsible for adjusting the instance pointer of the interface to the instance pointer of the class before jumping to the method of the class.

Oh, so this is some extra code that runs every time you call a method of an interface? I understand interfaces and use them sometimes, but not all the internal workings in the compiler and how they're implemented.

I have the feeling that you don't really know how interfaces work. ARC would have the exact same penalty as the reference counting of interfaces, because that is essentially what the compiler would use: call some function that increases/decreases the reference count of the instance. That is *exactly* what is done for interfaces. Also the interface is not created. It's part of the class instance. You simply have a pointer sized value that points to that. In the ideal situation of *only* working with the interface you can simply forget the class instance, cause the class will be freed once the reference count reached 0. Thus there will be *no* additional storage involved.

I'm talking about the decoupling of the interface and class and how that's a strange way to implement ARC. These wrapper functions and runtime lookups for casting are another side of it. I would hope someday we can implement ref counting in classes the same way as dynamic arrays and ansistrings, i.e. you just pass the reference and forget about it.

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Sven Barth: Ryan Joseph via fpc-pascal schrieb am Mi., 17. Feb. 2021, 02:15: Simply because no one…
On Feb 17, 2021, at 6:10 AM, Sven Barth wrote: Simply because no one has come around to implement it yet. The class type case is more complicated than the interface case. (Same would be true for record and objects) So, yes, we'll need to implement this first which would finally bring FPC up to Delphi compatibility regarding interface delegation.

So "class type method resolution" is what's missing? I never used the interface method resolution so I don't really understand this feature. What needs to happen as far as the compiler is concerned?

What if you want to implement virtual methods in a base class? This is kind of the core of "composition over inheritance" so it needs to be handled via method resolution I assume.

========================

(*
* Shape
*)

type
  TShape = class
    protected
      procedure Draw; virtual; abstract;
  end;

(*
* Circle
*)

type
  ICircle = interface
    procedure Draw;
  end;
type
  TCircle = class(ICircle)
    procedure Draw;
  end;

(*
* My Shape
*)

type
  TMyShape = class(TShape, ICircle)
    private
      m_circle: TCircle;
    protected
      procedure ICircle.Draw = Draw;
    public
      property circle: TCircle read m_circle implements ICircle;
  end;

Regards,
Ryan Joseph

Reply by email

Benito van der Zander
to Marco van de Voort: Op 2021-02-17 om 00:02 schreef Benito van der Zander via fpc-pascal: (1) A record is not…

Hi,

Show quoted text 6 lines
(1) A record is not a pointer. So that would require some implicit referencing in the property (2) If it was managed, it would be an allocation, so I don't understand this.

I am talking about replacing interfaces with an record.

For example this

/////////////////////////////////////
type ITest = interface
   procedure println;
end;
TTest = class(TInterfacedObject, ITest)
   v: integer;
   constructor Create(av: integer);
   procedure println;
end;
constructor TTest.Create(av: integer);
begin
   v := av;
end;
procedure TTest.println;
begin
   writeln(v);
end;
var c: ITest;
begin
   c := TTest.Create(123);
   c.println;
/////////////////////////////////

would become:

/////////////////////////////////
type
TTestRec = class
   rc: Integer;
   v: integer;
   constructor Create(av: integer);
   procedure println;
end;
RTest = record
   ptr: TTestRec;
   procedure println; inline;
   class operator :=(c: TTestRec): RTest;
   class operator Initialize(var aRec: RTest);
   class operator finalize(var aRec: RTest);
   class operator AddRef(var aRec: RTest);
end;
constructor TTestRec.Create(av: integer);
begin
   v := av;
end;
procedure TTestRec.println;
begin
   writeln(v);
end;
procedure RTest.println;
begin
   ptr.println; //the wrapper function is inlined
end;
class operator RTest.:=(c: TTestRec): RTest;
begin
   result := default(RTest);
   result.ptr := c;
   if c  nil then
     InterlockedIncrement(c.rc);
end;
class operator RTest.Initialize(var aRec: RTest);
begin
   aRec.ptr := nil;
end;
class operator RTest.finalize(var aRec: RTest);
begin
   if aRec.ptr  nil then
     if InterlockedDecrement(aRec.ptr.rc) = 0 then
       aRec.ptr.Free;
end;
class operator RTest.AddRef(var aRec: RTest);
begin
   if aRec.ptr  nil then
     InterlockedIncrement(aRec.ptr.rc);
end;
var
     r: RTest;
begin
   r := TTestRec.Create(123);
   r.println;
/////////////////////////////////////////////////////////

Or even replace the class with a record, too:

/////////////////////////////////////////////////////////
type
PTestRec2 = ^TTestRec2;
TTestRec2 = record
   rc: Integer;
   v: integer;
   class function Create(av: integer): PTestRec2; static;
   procedure println;
end;
RTest2 = record
   ptr: ^TTestRec2;
   procedure println; inline;
   class operator :=(c: PTestRec2): RTest2;
   class operator Initialize(var aRec: RTest2);
   class operator finalize(var aRec: RTest2);
   class operator AddRef(var aRec: RTest2);
end;
class function TTestRec2.Create(av: integer): PTestRec2;
begin
   new(result);
   result^.rc := 0;
   result^.v := av;
end;
procedure TTestRec2.println;
begin
   writeln(v);
end;
procedure RTest2.println;
begin
   ptr^.println;
end;
class operator RTest2.:=(c: PTestRec2): RTest2;
begin
   result := default(RTest2);
   result.ptr := c;
   if c  nil then
     InterlockedIncrement(c^.rc);
end;
class operator RTest2.Initialize(var aRec: RTest2);
begin
   aRec.ptr := nil;
end;
class operator RTest2.finalize(var aRec: RTest2);
begin
   if aRec.ptr  nil then
     if InterlockedDecrement(aRec.ptr^.rc) = 0 then
       dispose(aRec.ptr);
end;
class operator RTest2.AddRef(var aRec: RTest2);
begin
   if aRec.ptr  nil then
     InterlockedIncrement(aRec.ptr^.rc);
end;
var
     r2: RTest2;
begin
   r2 := TTestRec2.Create(123);
   r2.println;
/////////////////////////////////////////////////////////

Not sure if it is actually faster. That needs to be investigated.

But it definitely helps with the memory usage:

   writeln(ttest.InstanceSize);
   writeln(ttestrec.InstanceSize);
   writeln(sizeof(ttestrec2));

40
16
8

With many small objects it should be faster just because it fits better in the cache.

Cheers,
Benito

Marco van de Voort wrote 20 lines
Op 2021-02-17 om 00:02 schreef Benito van der Zander via fpc-pascal: >> >> And there often is a lot of unintentional deep copying. This is also >> why a property returning a record is fairly useless except for >> extremely small records like TPoint (and even that is not optimal no > > But a managed record to replace an interface, would only contain a > single pointer/class ref. That can be copied fast (1) A record is not a pointer. So that would require some implicit referencing in the property (2) If it was managed, it would be an allocation, so I don't understand this. _______________________________________________ fpc-pascal maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Benito van der Zander
Show quoted text 6 lines
On Feb 17, 2021, at 8:27 AM, Benito van der Zander via fpc-pascal wrote: var c: ITest; begin c := TTest.Create(123); c.println;

So this is where you're getting your performance penalties? Correct me if I'm wrong but two things happen here:

1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?)
2) calling "println" there is a call to a wrapper function (called "thunks")?

Regards,
Ryan Joseph

Reply by email

Adriaan van Os
to Ryan Joseph: So "class type method resolution" is what's missing? I never used the interface method…

To streamline the discussion, let me recapitulate:

1. multiple inheritance is nice to have, but it has the big issue that the inheritance tree becomes an inheritance graph and that makes overrules ambiguent.
2. interfaces don't have this issue with multiple inheritance, because they just declare, not implement
3. but that is also the weakness of interfaces, as we don't want to reimplement the same code each time
4. so, we really want an multiple-inheritance graph at the declaration level with clear tree-like unambigous inheritance paths at the implementation level
5. thus, the idea is to "push-in" implementation code into an interface that integrates fully at the declaration level but is independent at the implementation level.

So, the idea (and the purpose of the discussion) I think is to implement (5) in an elegant way.

Regards,

Adriaan van Os

Reply by email

Benito van der Zander
to Ryan Joseph: So this is where you're getting your performance penalties? Correct me if I'm wrong but…

Hi,

1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?)

just open the disassembler window in Lazarus and single step through all the instructions. Then you see everything

create alone calls a bunch of methods:

TInterfacedObject.NewInstance
TObject.NewInstance
getmem
TObject.InitInstance
fillchar
InitInterfacePointers
fpc_pushexceptaddr (on linux only?)
fpc_setjmp
TInterfacedObject.AfterConstruction

(interfaces with TInterfacedObject are even worse than implementing the interface yourself )

The implicit casting is very fast, a single assembly instruction:

add    $0x20,%rsi

But then it also calls fpc_intf_assign for reference counting

2) calling "println" there is a call to a wrapper function (called "thunks")?

Yes. Basically the wrapper function casts the interface back to the class (sub    $0x20,%rdi) before calling the actual method. Because the class method assumes self is the class and not an interface

Bye,
Benito

Ryan Joseph wrote 18 lines
> On Feb 17, 2021, at 8:27 AM, Benito van der Zander via fpc-pascal wrote: > > var c: ITest; > begin > c := TTest.Create(123); > c.println; So this is where you're getting your performance penalties? Correct me if I'm wrong but two things happen here: 1) Implicit cast to ITest which does a runtime lookup (but there's interface name so no string comparison like Supports?) 2) calling "println" there is a call to a wrapper function (called "thunks")? Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Benito van der Zander
Show quoted text 16 lines
On Feb 17, 2021, at 11:11 AM, Benito van der Zander via fpc-pascal wrote: just open the disassembler window in Lazarus and single step through all the instructions. Then you see everything create alone calls a bunch of methods: TInterfacedObject.NewInstance TObject.NewInstance getmem TObject.InitInstance fillchar InitInterfacePointers fpc_pushexceptaddr (on linux only?) fpc_setjmp TInterfacedObject.AfterConstruction

So where is your 10% performance hit coming from then?

on init:

InitInterfacePointers or TInterfacedObject.AfterConstruction?

otherwise, during the wrapper function? As Sven pointed out the ref counting happens with records operators also. ARC is general may just be slow if you implement it everywhere.

Regards,
Ryan Joseph

Reply by email

Benito van der Zander
to Ryan Joseph
Ryan Joseph wrote 5 lines
So where is your 10% performance hit coming from then? on init: InitInterfacePointers or TInterfacedObject.AfterConstruction?

I benchmarked it years ago, I do not remember the details.

But InitInterfacePointers was bad. Just look at it:

       procedure InitInterfacePointers(objclass: tclass;instance : pointer);
         var
           ovmt: PVmt;
           i: longint;
           intftable: pinterfacetable;
           Res: pinterfaceentry;
         begin
           ovmt := PVmt(objclass);
           while assigned(ovmt) and {$ifdef VER3_0}(ovmt^.vIntfTable
@emptyintf){$else}assigned(ovmt^.vIntfTable){$endif} do
             begin
               intftable:=ovmt^.vIntfTable;
               {$ifdef VER3_0}
               if assigned(intftable) then
               {$endif VER3_0}
               begin
                 i:=intftable^.EntryCount;
;
                 while i>0 do begin
                   if Res^.IType = etStandard then
                     ppointer(@(pbyte(instance)[Res^.IOffset]))^:=
                       pointer(Res^.VTable);
                   inc(Res);
                   dec(i);
                 end;
               end;
               ovmt:=ovmt^.vParent;
             end;
         end;

But reusing objects avoids calling all the functions from create. (but reusing is also expensive, besides the memory increase, it needs a free list and thread safe handling of the head of the list)

otherwise, during the wrapper function? As Sven pointed out the ref counting happens with records operators also. ARC is general may just be slow if you implement it everywhere.

The wrapper function was not included in the 10%

Reply by email

Ryan Joseph
to Benito van der Zander
Show quoted text 6 lines
On Feb 17, 2021, at 4:51 PM, Benito van der Zander via fpc-pascal wrote: I benchmarked it years ago, I do not remember the details. But InitInterfacePointers was bad. Just look at it:

That's not great for sure. Fillchar could be a big culprit also actually. I for one would like I way to disable that for special circumstances.

I don't Lazarus usually and I can't get the debugger to work on my Mac so tried Compiler Explorer. Not seeing the method calls here though.

https://godbolt.org/z/cjY3Te

Regards,
Ryan Joseph

Reply by email

Ryan Joseph
to Adriaan van Os: To streamline the discussion, let me recapitulate: 1. multiple inheritance is nice to…
Show quoted text 7 lines
On Feb 17, 2021, at 9:59 AM, Adriaan van Os via fpc-pascal wrote: 1. multiple inheritance is nice to have, but it has the big issue that the inheritance tree becomes an inheritance graph and that makes overrules ambiguent. 2. interfaces don't have this issue with multiple inheritance, because they just declare, not implement 3. but that is also the weakness of interfaces, as we don't want to reimplement the same code each time 4. so, we really want an multiple-inheritance graph at the declaration level with clear tree-like unambigous inheritance paths at the implementation level 5. thus, the idea is to "push-in" implementation code into an interface that integrates fully at the declaration level but is independent at the implementation level.

I would say that's right.

The problem is that, we like OOP inheritance but when we extend classes we are forced into a single hierarchy. We could use existing delegation patterns and dot-notation like obj.helper.DoSomething or by breaking out entirely and using plain functions but then we lose some of what makes OOP nice, which is, simply saying "something.DoFunction". It may be trivial in terms of typing but it's elegant and in my opinion clean code which is not tedious to write makes happy and more productive.

Here's a more practical example but there are other possibilities for composition patterns (I'l think of examples later). I copied this from the RTL and made some changes. Assume you have this hierarchy and you want to add some methods/data to JPEG and GIF images but not TIFF and PNG (they have some special compression needs or something like that). What we would do now is probably just dump them into TCustomBitmap (and bloat TIFF/PNG) or make another subclass just to store the extra methods (which is useless besides being a method store for 2 specific other classes).

What I want with traits is that I can extend those 2 classes with specific functionality, still retain the object-orientedness of the syntax and not get trapped trying to inject stuff into a hierarchy where it doesn't really belong anyways.

TTIFFBitmap TPNGBitmap TJPEGBitmap TGIFBitmap |
TCustomBitmap
|
TRasterImage
|
TGraphic
|
TPersistent
|
TObject

Regards,
Ryan Joseph

Reply by email

Benito van der Zander
to Ryan Joseph

Hi,

The problem is that, we like OOP inheritance but when we extend classes we are forced into a single hierarchy.

there is another way to extend classes without inheritance: type helpers

Traits are like reverse type helpers. With the type helper you first declare the class and then the extending helper.

=== code begin ===
type
   TTest = class(TObject)
   public
     procedure test(a: integer);
   end;
   TTrait = class helper for TTest
     procedure test2;
   end;
procedure TTest.test(a: integer);
begin
   writeln(a);
end;
procedure TTrait.test2;
begin
   test(10);
end;
//var x: ttest2;
//x.test2

=== code end ===

With traits you could first define the extension, and then the class:

=== code begin ===
type
   TTrait = trait
     procedure test2;
   end;
   TTest = class(TObject, TTrait)
   public
     procedure test(a: integer);
   end;
procedure TTest.test(a: integer);
begin
   writeln(a);
end;
procedure TTrait.test2;
begin
   test(10); //here it needs to know the test function exist. Perhaps
declare it in the the trait, too?
end;
//var x: ttest2;
//x.test2
=== code end ===

But otherwise, it looks exactly the same

Perhaps the type helper code could be reused for traits

Cheers,
Benito

Ryan Joseph wrote 38 lines
> On Feb 17, 2021, at 9:59 AM, Adriaan van Os via fpc-pascal wrote: > > 1. multiple inheritance is nice to have, but it has the big issue that the inheritance tree becomes an inheritance graph and that makes overrules ambiguent. > 2. interfaces don't have this issue with multiple inheritance, because they just declare, not implement > 3. but that is also the weakness of interfaces, as we don't want to reimplement the same code each time > 4. so, we really want an multiple-inheritance graph at the declaration level with clear tree-like unambigous inheritance paths at the implementation level > 5. thus, the idea is to "push-in" implementation code into an interface that integrates fully at the declaration level but is independent at the implementation level. I would say that's right. The problem is that, we like OOP inheritance but when we extend classes we are forced into a single hierarchy. We could use existing delegation patterns and dot-notation like obj.helper.DoSomething or by breaking out entirely and using plain functions but then we lose some of what makes OOP nice, which is, simply saying "something.DoFunction". It may be trivial in terms of typing but it's elegant and in my opinion clean code which is not tedious to write makes happy and more productive. Here's a more practical example but there are other possibilities for composition patterns (I'l think of examples later). I copied this from the RTL and made some changes. Assume you have this hierarchy and you want to add some methods/data to JPEG and GIF images but not TIFF and PNG (they have some special compression needs or something like that). What we would do now is probably just dump them into TCustomBitmap (and bloat TIFF/PNG) or make another subclass just to store the extra methods (which is useless besides being a method store for 2 specific other classes). What I want with traits is that I can extend those 2 classes with specific functionality, still retain the object-orientedness of the syntax and not get trapped trying to inject stuff into a hierarchy where it doesn't really belong anyways. TTIFFBitmap TPNGBitmap TJPEGBitmap TGIFBitmap | TCustomBitmap | TRasterImage | TGraphic | TPersistent | TObject Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal

Reply by email

Ryan Joseph
to Benito van der Zander
On Feb 18, 2021, at 10:40 AM, Benito van der Zander via fpc-pascal wrote: Traits are like reverse type helpers. With the type helper you first declare the class and then the extending helper.

Indeed, but with the crucial distinction that helpers don't allow fields, only methods.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph
Ryan Joseph wrote 6 lines
> On Feb 18, 2021, at 10:40 AM, Benito van der Zander via fpc-pascal wrote: > > Traits are like reverse type helpers. With the type helper you first declare the class and then the extending helper. Indeed, but with the crucial distinction that helpers don't allow fields, only methods.

And they are differently scoped.

There is another approach, but generics do not yet support that

type
   TFooBase = class
   end;
   TTrait = trait
     procedure Bar;
   end;
   TFoo = class(TFooBase)
     _trait: TTrait;
      procedure Foo;
   end;

could be written as

   generic TTrait<T> = class(T)
     procedure Bar;
   end;
   TMyFoo = class(specialize TTrait<TFooBase>)
      procedure Foo;
   end;

Of course that can get out of hand, if you want to include many traits.

---------

On the other hand, it even would allow support for the Trait to access methods from TFoo...

   generic TTrait<TBase, THost> = class(TBase)
     procedure Bar; // can do    THost(self).Foo()
   end;

  TMyFoo = class(specialize TTrait<TFooBase, TMyFoo>)      procedure Foo;  // can call Bar()
  end;

Of course assuming that TMyFoo can already be passed during specialization.

Reply by email

Sven Barth
to Martin Frb
Martin Frb wrote 11 lines
could be written as   generic TTrait<T> = class(T)     procedure Bar;   end;   TMyFoo = class(specialize TTrait<TFooBase>)      procedure Foo;   end; Of course that can get out of hand, if you want to include many traits.

You need to constrain T as a TObject, then it works.

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: That's not great for sure. Fillchar could be a big culprit also actually. I for one would…
Ryan Joseph wrote 8 lines
> On Feb 17, 2021, at 4:51 PM, Benito van der Zander via fpc-pascal wrote: > > I benchmarked it years ago, I do not remember the details. > > But InitInterfacePointers was bad. Just look at it: > That's not great for sure. Fillchar could be a big culprit also actually. I for one would like I way to disable that for special circumstances.

You can in theory circumvent this by overriding NewInstance for some class though you'll have to reimplement essentially the whole of TObject.InitInstance.

I don't Lazarus usually and I can't get the debugger to work on my Mac so tried Compiler Explorer. Not seeing the method calls here though.

The whole class instantiation except for the allocation is hidden in TObject.InitInstance.

Regards,
Sven

Reply by email

Sven Barth
to Ryan Joseph: So "class type method resolution" is what's missing? I never used the interface method…
Ryan Joseph wrote 6 lines
> On Feb 17, 2021, at 6:10 AM, Sven Barth wrote: > > Simply because no one has come around to implement it yet. The class type case is more complicated than the interface case. (Same would be true for record and objects) > So, yes, we'll need to implement this first which would finally bring FPC up to Delphi compatibility regarding interface delegation. So "class type method resolution" is what's missing? I never used the interface method resolution so I don't really understand this feature. What needs to happen as far as the compiler is concerned?

The problem is this: if you have a property with an interface type the compiler can simply build the interface's VMT for this. However for classes (or objects or records) the compiler needs to generate slightly different thunks that fixup the correct Self value, especially if things are mixed between the subclass and the container class.

Show quoted text 42 lines
What if you want to implement virtual methods in a base class? This is kind of the core of "composition over inheritance" so it needs to be handled via method resolution I assume. ======================== (* * Shape *) type TShape = class protected procedure Draw; virtual; abstract; end; (* * Circle *) type ICircle = interface procedure Draw; end; type TCircle = class(ICircle) procedure Draw; end; (* * My Shape *) type TMyShape = class(TShape, ICircle) private m_circle: TCircle; protected procedure ICircle.Draw = Draw; public property circle: TCircle read m_circle implements ICircle; end;

Your specific example is rather useless, cause your interface only has a single method that you redirect to the class' method thus you wouldn't need to use interface delegation. But anyway, your code would generate a runtime error, because your TMyShape.Draw would still be abstract.

Regards,
Sven

Reply by email

Martin Frb
to Sven Barth: You need to constrain T as a TObject, then it works. Sven
Sven Barth wrote
You need to constrain T as a TObject, then it works.

Ah that works. With 2 restrictions

TBase can be any class, since a trait can be applied to any class. So the only known common base is TObject.

(restriction 1)
But "TBase: TObject" means that the trait can not access Self.Base, even if the latter passed in type has such a method.
Same with (note the "class" keyword)
     generic TTrait<TBase: class; THost> = class(TBase)

Normally if the param to a generic is not restricted you can write T.anything, and during specialization this will be resolved or fail. So that ability is lost.

As a workaround THost can be passed in.
(restriction 2)
That requires a forward declaration of TFoo. Otherwise it fails.

type
   TFooBase = class
     procedure Base;
   end;
   generic TTrait<TBase: TObject; THost> = class(TBase)
     procedure Bar;
   end;
   TMyFoo = class;
   TMyFoo = class(specialize TTrait<TFooBase, TMyFoo>)
      procedure Foo;
   end;
procedure TTrait.Bar;
begin
   //Self.Base; // fails
   THost(Self).Base;
   THost(Self).Foo;
end;

Reply by email

Ryan Joseph
to Sven Barth: The problem is this: if you have a property with an interface type the compiler can…
Show quoted text 5 lines
On Feb 18, 2021, at 3:07 PM, Sven Barth wrote: > So "class type method resolution" is what's missing? I never used the interface method resolution so I don't really understand this feature. What needs to happen as far as the compiler is concerned? The problem is this: if you have a property with an interface type the compiler can simply build the interface's VMT for this. However for classes (or objects or records) the compiler needs to generate slightly different thunks that fixup the correct Self value, especially if things are mixed between the subclass and the container class.

Then this is the real challenge to implement a "default implements property" and something I know nothing about. :) The rest is pretty easy actually though.

Show quoted text 12 lines
> type > TMyShape = class(TShape, ICircle) > private > m_circle: TCircle; > protected > procedure ICircle.Draw = Draw; > public > property circle: TCircle read m_circle implements ICircle; > end; Your specific example is rather useless, cause your interface only has a single method that you redirect to the class' method thus you wouldn't need to use interface delegation. But anyway, your code would generate a runtime error, because your TMyShape.Draw would still be abstract.

Maybe it's a bad example but I'm trying to illustrate how method resolution could be used for overriding (or what ever it would be called in this context.

I know this isn't correct syntax but it's more of this direction:

procedure Draw =  ICircle.Draw;

TShape.Draw is resolved by ICircle.Draw, which is implemented via TCircle. It's quasi-overriding but the VMT table should point to m_circle.Draw so Draw could be called on TShape and get property resolved by the "implements ICircle" property. Hope that makes sense.

Regards,
Ryan Joseph

Reply by email

Martin Frb
to Ryan Joseph: I'm not really understand 're still using subclassing, which is the thing we're trying to…
Ryan Joseph wrote 10 lines
> On Feb 18, 2021, at 12:33 PM, Martin Frb via fpc-pascal wrote: > > TMyFoo = class(specialize TTrait<TFooBase>) > procedure Foo; > end; > > Of course that can get out of hand, if you want to include many traits. I'm not really understand 're still using subclassing, which is the thing we're trying to avoid.

Yes and Yes, and maybe Why?

I agree that a dedicated traits solution would be cleaner, and may have advances.
For example in the above, the trait can not have a base-trait. (Well it can, but it get messy, really messy).
Multiple traits via generic => messy.
And also the conflict resolution behaviour differs. ...

However what is the problem of the subclassing?

1) From the user of the class (the person using the class in their code) all methods are in the desired namespace.

2) There is (almost) no performance penality.
Well perfomance:
  SomeFoo is TXXXX will need to step the extra classes inbetween.   And so will  fpc assert objec checks, if enbled. (but thats debugging)

But otherwise I can't think were the resulting class would even need one CPU tick more. Calling a method (with the exact same signature) should generate the exact same code, independent if it was declared in the current or in the base class. (at least if it is not overriden/reintroduced in the current, but then you would need to call it with the "inherited" keyword, so that would be different.) none virtual method => known address, just call
virtual method => address from know offset in the VMT. In both cases the VMT of the current class. Same thing.

And yes, their is memory. Their is one single block of memory for the class data. (It is only once, even if you have 1000 instances). So what?

One could have a WPO optimization, to remove classes from the tree, if they are not needed.
- no reference to the class itself: casting, ".. is .." (not possible here, since anonymous)
- access via ClassType, ParentClassType etc
- no difference in the VMT compared to the next higher class
- not sure maybe others
But its a lot of work in order to save a few bytes in the exe.

Reply by email

Sven Barth
to Ryan Joseph: Then this is the real challenge to implement a "default implements property" and…
Ryan Joseph wrote 6 lines
> On Feb 18, 2021, at 3:07 PM, Sven Barth wrote: > >> So "class type method resolution" is what's missing? I never used the interface method resolution so I don't really understand this feature. What needs to happen as far as the compiler is concerned? > The problem is this: if you have a property with an interface type the compiler can simply build the interface's VMT for this. However for classes (or objects or records) the compiler needs to generate slightly different thunks that fixup the correct Self value, especially if things are mixed between the subclass and the container class. Then this is the real challenge to implement a "default implements property" and something I know nothing about. :) The rest is pretty easy actually though.

Correct, that is the real challenge ;)

Show quoted text 17 lines
>> type >> TMyShape = class(TShape, ICircle) >> private >> m_circle: TCircle; >> protected >> procedure ICircle.Draw = Draw; >> public >> property circle: TCircle read m_circle implements ICircle; >> end; > Your specific example is rather useless, cause your interface only has a single method that you redirect to the class' method thus you wouldn't need to use interface delegation. But anyway, your code would generate a runtime error, because your TMyShape.Draw would still be abstract. Maybe it's a bad example but I'm trying to illustrate how method resolution could be used for overriding (or what ever it would be called in this context. I know this isn't correct syntax but it's more of this direction: procedure Draw = ICircle.Draw; TShape.Draw is resolved by ICircle.Draw, which is implemented via TCircle. It's quasi-overriding but the VMT table should point to m_circle.Draw so Draw could be called on TShape and get property resolved by the "implements ICircle" property. Hope that makes sense.

Let me get it straight: your example is that both TShape and TCircle provide a Draw method, but in TMyShape you want TCircle.Draw to be used instead of TShape.Draw when TMyShape.Draw is called. Correct so far?

Well that would work without any additional declarations. The idea would be that the "default" modifier would lead to conflicts only if a Draw method exists in TMyShape itself. If it's declared in one of the parents then that won't matter.

The only thing that will not work and which will continue not to work is that m_circle.Draw will not be called if you access the TMyShape through a TShape.Draw call. For that one needs to use an explicit declaration:

=== code begin ===

type
   TMyShape = class(TShape, ICircle)
   private
     m_circle: TCircle;
   public
     procedure ICircle.Draw = Draw;
     property Circle: TCircle read m_circle implements ICircle;
     procedure Draw; override;
   end;
procedure TMyShape.Draw;
begin
   m_circle.Draw;
end;

=== code end ===

This way it does not matter if TCircle is a class, a record or an object, cause it will always work correctly.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
Show quoted text 22 lines
On Feb 18, 2021, at 11:30 PM, Sven Barth wrote: The only thing that will not work and which will continue not to work is that m_circle.Draw will not be called if you access the TMyShape through a TShape.Draw call. For that one needs to use an explicit declaration: === code begin === type TMyShape = class(TShape, ICircle) private m_circle: TCircle; public procedure ICircle.Draw = Draw; property Circle: TCircle read m_circle implements ICircle; procedure Draw; override; end; procedure TMyShape.Draw; begin m_circle.Draw; end; === code end ===

So the method resolution doesn't actually change the VMT table of the class, just the interface? That's unfortunate because being able to affect the VMT by method resolution would open up a lot of possibilities for polymorphism using composition. Maybe writing wrapper functions is good enough for this. Not sure, I need to do some research into old code and explore things a bit more.

I just realized another potential problem. If we use the "default" keyword that means there could be multiple "defaults" unless we limit the property to 1 per class, which would kind of defeat the purpose of the feature (like the issue we had with helpers and made multi-helpers for).

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph

Ryan Joseph via fpc-pascal schrieb am
Fr., 19. Feb. 2021, 17:32:

Btw, here's point of reference for this other proposed syntax. https://github.com/genericptr/freepascal/wiki/Default-Implements-Property

Your example is not quite correct and it's also not really complete: if a class implements an interface there *must* be a parent class mentioned (at least TObject). Also your example should demonstrate that one can call TMyShape.Draw without having to retrieve the interface (after all that's the point of the interface property).

Additionally support for raw interfaces (aka Corba) should be added for interface delegation if it isn't already supported. (Otherwise your TMyShape either needs to inherit from e.g. TInterfacedObject or one of the involved types needs to implement the methods of IInterface.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Feb 19, 2021, at 11:01 AM, Sven Barth via fpc-pascal wrote: Your example is not quite correct and it's also not really complete: if a class implements an interface there *must* be a parent class mentioned (at least TObject). Also your example should demonstrate that one can call TMyShape.Draw without having to retrieve the interface (after all that's the point of the interface property).

TObject is always the root class I thought? Why is it different here?

Additionally support for raw interfaces (aka Corba) should be added for interface delegation if it isn't already supported. (Otherwise your TMyShape either needs to inherit from e.g. TInterfacedObject or one of the involved types needs to implement the methods of IInterface.

They work already fortunately.

Regards,
Ryan Joseph

Reply by email

Marco van de Voort
to Ryan Joseph: So the method resolution doesn't actually change the VMT table of the class, just the…

Op 2021-02-19 om 16:50 schr

So the method resolution doesn't actually change the VMT table of the class, just the interface?

See it like this: An interface is a fragment of VMT. All methods of the interface are there in order.

If implemented right, it is like a pointer into the implementing classes' VMT table.

The IVT is a mapping of interface identifiers (in COM terms, GUIDS) mapped to the offset into the VMT they apply too.

Reply by email

Sven Barth
to Ryan Joseph: TObject is always the root class I thought? Why is it different here? They work already…
Ryan Joseph wrote 6 lines
> On Feb 19, 2021, at 11:01 AM, Sven Barth via fpc-pascal wrote: > > Your example is not quite correct and it's also not really complete: if a class implements an interface there *must* be a parent class mentioned (at least TObject). Also your example should demonstrate that one can call TMyShape.Draw without having to retrieve the interface (after all that's the point of the interface property). > TObject is always the root class I thought? Why is it different here?

TObject *is* always the root, but the first entry of the inheritance list of a class *must* be a class as well.

> Additionally support for raw interfaces (aka Corba) should be added for interface delegation if it isn't already supported. (Otherwise your TMyShape either needs to inherit from e.g. TInterfacedObject or one of the involved types needs to implement the methods of IInterface. > They work already fortunately.

Good. :)

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth
On Feb 19, 2021, at 3:14 PM, Sven Barth via fpc-pascal wrote: TObject *is* always the root, but the first entry of the inheritance list of a class *must* be a class as well.

I'm testing interface delegation now in ObjFPC mode and I don't see that including TObject is required.

Regards,
Ryan Joseph

Reply by email

Mattias Gaertner
to Ryan Joseph
Show quoted text 8 lines
> On Feb 19, 2021, at 3:14 PM, Sven Barth via fpc-pascal > wrote: > > TObject *is* always the root, but the first entry of the > inheritance list of a class *must* be a class as well. I'm testing interface delegation now in ObjFPC mode and I don't see that including TObject is required.

In Delphi the first must be a class. FPC allows to omit the class and uses TObject as default.

Mattias

Reply by email

Ryan Joseph
to Ryan Joseph: So the method resolution doesn't actually change the VMT table of the class, just the…
On Feb 19, 2021, at 8:50 AM, Ryan Joseph wrote: I just realized another potential problem. If we use the "default" keyword that means there could be multiple "defaults" unless we limit the property to 1 per class, which would kind of defeat the purpose of the feature (like the issue we had with helpers and made multi-helpers for).

Can you respond to this concern? It could be a deal breaker for the syntax.

Regards,
Ryan Joseph

Reply by email

Sven Barth
to Ryan Joseph

Ryan Joseph via fpc-pascal schrieb am
Mo., 22. Feb. 2021, 10:07:

Show quoted text 11 lines
> On Feb 19, 2021, at 8:50 AM, Ryan Joseph wrote: > > I just realized another potential problem. If we use the "default" keyword that means there could be multiple "defaults" unless we limit the property to 1 per class, which would kind of defeat the purpose of the feature (like the issue we had with helpers and made multi-helpers for). Can you respond to this concern? It could be a deal breaker for the syntax.

It depends on how "default" is defined. If we define it as "moves the interface into the class' namespace" there is no problem as long as there are no other conflicts (e.g. two interfaces with a Draw method without further parameters that should still be handled by two different delegates => simply not possible).

I *think* Delphi allows multiple default array properties as long as they have different index types (e.g. String vs Integer), though I'd need to test this.

But independent of that I wouldn't have a problem to define "default" for a delegation property in the way mentioned above.

Regards,
Sven

Reply by email

Ryan Joseph
to Sven Barth

Ok my summary thus far with this syntax (default interface delegation) is that composition is possible (which is very nice) but polymorphism is not really possible so it's not exactly an alternative to multiple inheritance. Besides the complications of implementing method resolution for classes (no idea what's involved here) I would say it's a pretty low time investment to get a good chunk of what people want from multiple inheritance, although not a full fledged alternative like adding a new "trait" syntax would provide.

I want to add more examples to the GitHub page so we don't forget. The most important thing to get right is not allowing any possibility to write have ambiguous methods. Here are some common ones I could think of. Are these correct? Anything to add? I guess the compiler just gives "already declared" errors for each method with a conflict. Does the visibility of the property matter in subclasses (I assume yes)?

(****************************************************************************************)
type
  ICircle = interface
    procedure Draw;
  end;
type
  TCircle = record
    procedure Draw;
  end;
type
  TMyShape = class(ICircle)
    private
      FCircle: TCircle;
    public
      property Circle: TCircle read FCircle implements ICircle; default;
  end;
type
  TOtherShape = class(TMyShape)
    public
      // ERROR - draw is already declared
      procedure Draw;
  end;
(****************************************************************************************)
type
  TMyShape = class(ICircle)
    private
      FCircle: TCircle;
    public
      property Circle: TCircle read FCircle implements ICircle; default;
      // ERROR - Draw already declared
      procedure Draw;
  end;
(****************************************************************************************)
type
  TMyShape = class(ICircle)
    private
      FCircle: TCircle;
    public
      property Circle: TCircle read FCircle implements ICircle; default;
      // Conflict is resolved, we can now redeclare Draw and Circle.Draw will point to TMyShape.Draw instead of TCircle.Draw
      procedure ICircle.Draw = Draw;
      procedure Draw;
  end;
(****************************************************************************************)
type
  ISquare = interface
    procedure Draw;
  end;
type
  TSquare = record
    procedure Draw;
  end;
type
  TOtherShape = class(TMyShape, ISquare)
    private
      FSquare: TSquare;
    public
      // ERROR - Draw is already declared
      property Square: TSquare read FSquare implements ISquare; default;
  end;

Regards,
Ryan Joseph

Reply by email