Am 14.02.2021 um 02:45 schrieb Ryan Joseph via fpc-pascal:
On Feb 13, 2021, at 12:38 PM, Sven Barth <pascaldra...@googlemail.com> 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).
=== 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
_______________________________________________
fpc-pascal maillist - fpc-pascal@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal