with in classes/records

classic Classic list List threaded Threaded
44 messages Options
123
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Free Pascal - General mailing list
Ryan Joseph <[hidden email]> schrieb am Di., 4. Sep. 2018, 11:27:


> On Sep 4, 2018, at 2:06 PM, Ryan Joseph <[hidden email]> wrote:
>
> Sorry I didn’t think enough before I sent this.
>
> We *must* allow this assignment to make operator overloads work. +=  operators are also basically assigning TWrapper to TWrapper, right? I guess we need to break the default property behavior is instances that the same type is being assigned to itself but correct me if I’m wrong.
>
> var
>       wrapper: TWrapper;
>
> wrapper += 10;

Some questions about operator overloads.

1) rec := 1; should resolve to rec.num := 1 obviously.

2) rec += 10; should call the function TWrapper.+ right? It could operate directly on the field “num” but then the actual function wouldn’t be called.

Would you please stop thinking with the C operators? They are merely syntactic sugar and they don't exist by themselves. For this topic at least please stick to their full versions (in your example "rec := rec + 10") as that highlights better what the compiler needs to handle. In this case both the + *and* the assignment operator. 


3) should writeln(rec); resolve to writeln(rec.num); or be a syntax error? If it resolves to rec.num then passing around the record would technically just pass the num field and not the record. That doesn’t sound right to me. Without thinking about it much it feels like “rec” in isolation should be treated as the base type, ie. TWrapper.

4) I guess := operator overloads for records with a default property should be disabled, right? Otherwise they present a conflict that needs to be resolved.

The idea of the default property is that *all* operators (and methods) (except management operators) are hoisted from the type of the default property. The assignment of one record with default property to another of the same type is handled by the Copy management operator. 

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3


> On Sep 4, 2018, at 7:15 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> Would you please stop thinking with the C operators? They are merely syntactic sugar and they don't exist by themselves. For this topic at least please stick to their full versions (in your example "rec := rec + 10") as that highlights better what the compiler needs to handle. In this case both the + *and* the assignment operator.
>

Sorry, please bear with me.

I think this is just a precedence issue I raised. If TWrapper has a + operator then:

wrapper := wrapper + 1

should call the + operator, because base class takes precedence over the field num: integer. Correct? If TWrapper doesn’t have a + operator then:

wrapper := wrapper + 1 should resolve to wrapper.num := wrapper.num + 1

right?

type
        TWrapper = record
                num: integer;
                property _default: integer read num write num; default;
                class operator + (left: TWrapper; right: integer): TWrapper;
        end;

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
In reply to this post by Free Pascal - General mailing list


> On Sep 4, 2018, at 7:15 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> The idea of the default property is that *all* operators (and methods) (except management operators) are hoisted from the type of the default property. The assignment of one record with default property to another of the same type is handled by the Copy management operator.
>

I just realized that default shouldn’t be allowed on non-object fields right? I think that’s what was decided and makes most sense to me too. Disregard my last email. Sorry if I make a bunch of noise while I’m working on this but I’m probably going to have lots of questions coming up.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3


> On Sep 5, 2018, at 11:22 AM, Ryan Joseph <[hidden email]> wrote:
>
> I just realized that default shouldn’t be allowed on non-object fields right? I think that’s what was decided and makes most sense to me too. Disregard my last email. Sorry if I make a bunch of noise while I’m working on this but I’m probably going to have lots of questions coming up.

Are default properties always read/write or can they be any combination? As they’re intended to be used it seems like they’re always both but maybe the option should be left open.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
In reply to this post by Free Pascal - General mailing list

> On Sep 4, 2018, at 12:35 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>
> I think you need to be clearer what you want to achieve in the end. The
> default property as intended by Maciej has the idea that it hoists the
> operators of the default property type to the record it is contained in.

Here’s my usage example which I encountered and got stuck with.

The problem is I have a base class that has some commonly used functionality across many subclasses but at a later time I wanted to remove the fields from the class to save memory. Now I’m stuck with an OOP problem where I have tons of syntax like obj.RegisterMethod which are used in dozens of subclasses.

The solutions currently are:

1) Put the two manager concepts into separate classes which can be subclassed. This may work in some instances but in others it breaks class hierarchies. If I want to include both I’m in trouble also because there’s no multiple inheritance in Pascal.

2) Put the two manager concepts into separate delegate classes which are included manually (delphi has the “implements” property but this doesn’t really do anything to help). This is more flexible but now I need to replace all calls as obj.methodManager.RegisterMethod which is 1) messy/long 2) introduces more names to remember and 3) will require lots of copy/paste. If I ever want to change it back I have hours of copy/pasting to do.

type
        TBaseObject = class
                private
                        methods: array of TMethod;
                        objects: array of TObject;
                public
                        { method manager }
                        procedure RegisterMethod(name: string; proc: pointer);
                        procedure InvokeMethod(name: string);
                        { object manager }
                        procedure ManageObject(obj: TObject);
        end;

// if we need either of the 2 managers we need to inherit from TBaseObject
// and take on the baggage. Typical OOP problems with deep class hierarchies.

type
        TMyObject = class (TBaseObject)
        end;
var

        obj: TMyObject;
begin
        obj := TMyObject.Create;
        obj.RegisterMethod('foo', @method);
        obj.InvokeMethod('foo');
        obj.ManageObject(otherObj);
end.

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

Using what I’ll call the “alias property” for now (using “with” is not proper grammar Sven mentioned, and “default” implies just one, so...) we can pull out the two manager concepts into separate records and include them manually into any class we want. Thanks to having management operators now we can get automatic init/finalize to manage the lifecycle but I didn’t show that here.

The alias property acts like the default property we’ve been discussing by calling into the fields namespace without the property name. This is the preferred solution for breaking up the class because once I add the manager record and the alias I don’t need to change any other code, everything works like before.

What I propose is that the default property be allowed to support multiple default properties. The default property already basically does this so it’s just a matter of performing some searches before determining which property to use for the given call. If we made this safe it would be a good alternative to subclassing and allow for some really nice delegation patterns we can’t do in Pascal now.

Clearly there are problems with name conflicts and precedence but surely they can be solved. There’s already a precedent of multiple interfaces in classes and redefining methods in subclasses so I don’t see why this can’t be made safe also. Please note the default property already introduces these problems so in my opinion it’s not that crazy to add more levels of search than just one.

Here’s what the class looks like broken up into delegates and using aliases so they calling conventions don’t change from the original.

type
        TMethodManager = record
                private
                        methods: array of TMethod;
                public
                        procedure RegisterMethod(name: string; proc: pointer);
                        procedure InvokeMethod(name: string);
        end;

type
        TObjectManager = record
                private
                        objects: array of TObject;
                public
                        procedure ManageObject(obj: TObject);
        end;

type
        TMyObjectA = class
                private
                        m_methodManager: TMethodManager;
                        property methodManager: TMethodManager alias m_methodManager;
        end;

type
        TMyObjectB = class
                private
                        m_objectManager: TObjectManager;
                        property objectManager: TObjectManager alias m_objectManager;
        end;

var
        objA: TMyObjectA;
        objB: TMyObjectB;
begin

        objA := TMyObjectA.Create;
        objA.RegisterMethod('foo', @method);
        objA.InvokeMethod('foo’);

        objB := TMyObjectB.Create;
        objB.ManageObject(otherObj);
end.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Michael Van Canneyt
In reply to this post by Ryan Joseph-3


On Wed, 5 Sep 2018, Ryan Joseph wrote:

>
>
>> On Sep 4, 2018, at 7:15 PM, Sven Barth via fpc-pascal <[hidden email]> wrote:
>>
>> The idea of the default property is that *all* operators (and methods) (except management operators) are hoisted from the type of the default property. The assignment of one record with default property to another of the same type is handled by the Copy management operator.
>>
>
> I just realized that default shouldn’t be allowed on non-object fields
> right?  I think that’s what was decided and makes most sense to me too.
> Disregard my last email.  Sorry if I make a bunch of noise while I’m
> working on this but I’m probably going to have lots of questions coming
> up.
No, the whole point of default is that they should be for any kind of field.
For example if you want a nullable boolean, you'll do something like

Type
     TNullable<T>= Record
     Private
       F : T;
       isAssigned : Boolean;
       Function GetValue : T;
       Procedure SetValue(aValue : T);
       Property Value : T Read GetValue Write SetValue; default;
       Property IsNull : Boolean Read GetISNull Write SetIsNull;
     end;

Var
   B : TNullable<Boolean>;

begin
   Writeln(B.IsNull); // Should be true
   B:=True;           // Works due to default.
   Writeln(B.IsNull); // Should be false.
end.

Michael.








>
> Regards,
> Ryan Joseph
>
> _______________________________________________
> fpc-pascal maillist  -  [hidden email]
> http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Michael Van Canneyt
In reply to this post by Ryan Joseph-3


On Thu, 6 Sep 2018, Ryan Joseph wrote:

>
>
>> On Sep 5, 2018, at 11:22 AM, Ryan Joseph <[hidden email]> wrote:
>>
>> I just realized that default shouldn’t be allowed on non-object fields right? I think that’s what was decided and makes most sense to me too. Disregard my last email. Sorry if I make a bunch of noise while I’m working on this but I’m probably going to have lots of questions coming up.
>
> Are default properties always read/write or can they be any combination?
> As they’re intended to be used it seems like they’re always both but maybe
> the option should be left open.

The option should be open. We don't know what the possible use cases will
be.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
In reply to this post by Michael Van Canneyt


> On Sep 6, 2018, at 9:25 PM, Michael Van Canneyt <[hidden email]> wrote:
>
> No, the whole point of default is that they should be for any kind of field.
> For example if you want a nullable boolean, you'll do something like
>
> Type
>    TNullable<T>= Record
>    Private
>      F : T;
>      isAssigned : Boolean;
>      Function GetValue : T;
>      Procedure SetValue(aValue : T);
>      Property Value : T Read GetValue Write SetValue; default;
>      Property IsNull : Boolean Read GetISNull Write SetIsNull;
>    end;

Thanks for the example. I didn’t even know it was intended for the default property to have functions as the read/write values. I thought read/write was always going to map directly to a field.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Free Pascal - General mailing list
Ryan Joseph <[hidden email]> schrieb am Do., 6. Sep. 2018, 16:33:


> On Sep 6, 2018, at 9:25 PM, Michael Van Canneyt <[hidden email]> wrote:
>
> No, the whole point of default is that they should be for any kind of field.
> For example if you want a nullable boolean, you'll do something like
>
> Type
>    TNullable<T>= Record
>    Private
>      F : T;
>      isAssigned : Boolean;
>      Function GetValue : T;
>      Procedure SetValue(aValue : T);
>      Property Value : T Read GetValue Write SetValue; default;
>      Property IsNull : Boolean Read GetISNull Write SetIsNull;
>    end;

Thanks for the example. I didn’t even know it was intended for the default property to have functions as the read/write values. I thought read/write was always going to map directly to a field.

No, it was always (as far as the idea exists at least) the idea that all abilities of a property should be available for non-indexed default properties as well. 

Regards, 
Sven 

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
It seems syntacticly possible that default properties could be recursive by having a default property reference a record/class with another default property. Should that be allowed?

type
        THelperB = record
                field: integer;
        end;

type
        THelperA = record
                obj: THelperB;
                property helperB: THelperB read obj write obj; default;
        end;


type
        TWrapper = record
                obj: THelperA;
                property helperA: THelperA read obj write obj; default;
        end;


var
        wrapper: TWrapper;
begin
        wrapper.field := 100; // wrapper.helperA.helperB.field := 100

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Martin Friebe
On 09/09/2018 10:48, Ryan Joseph wrote:
> It seems syntacticly possible that default properties could be recursive by having a default property reference a record/class with another default property. Should that be allowed?
>

And how should the rules be for mixing with class helpers? (In case of
conflicting names)
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3


> On Sep 9, 2018, at 5:01 PM, Martin <[hidden email]> wrote:
>
> And how should the rules be for mixing with class helpers? (In case of conflicting names)

On the dev list Sven told me to use the tcallcandidates class to resolve overloads and I believe this handles searching class helpers also. So I guess the answer is whatever is standard behavior in FPC.

Since the base class takes precedence the example would search in the order:

TWrapper
THelperA
THelperB

If that’s allowed it’s basically the same as subclassing but in records.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Michael Van Canneyt
In reply to this post by Ryan Joseph-3


On Sun, 9 Sep 2018, Ryan Joseph wrote:

> It seems syntacticly possible that default properties could be recursive by having a default property reference a record/class with another default property. Should that be allowed?

I don't think so. Let's start with 1 level.
For default arrays it's also only one level deep.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Free Pascal - General mailing list
Am 09.09.2018 um 16:16 schrieb Michael Van Canneyt:

>
>
> On Sun, 9 Sep 2018, Ryan Joseph wrote:
>
>> It seems syntacticly possible that default properties could be
>> recursive by having a default property reference a record/class with
>> another default property. Should that be allowed?
>
> I don't think so. Let's start with 1 level.
> For default arrays it's also only one level deep.
Ehm... news flash:

=== code begin ===

{$mode objfpc}

type
   TTest1 = class
   private
     fField: LongInt;
     function GetStuff(Index: Integer): Integer;
   public
     property Stuff[Index: Integer]: Integer read GetStuff; default;
   end;

   TTest2 = class
   private
     function GetTest(Index: Integer): TTest1;
   public
     property Test[Index: Integer]: TTest1 read GetTest; default;
   end;

{ TTest2 }

function TTest2.GetTest(Index: Integer): TTest1;
begin
   Result := TTest1.Create;
   Result.fField := Index;
end;

{ TTest1 }

function TTest1.GetStuff(Index: Integer): Integer;
begin
   Result := Index * fField;
end;

var
   Test: TTest2;
begin
   Test := TTest2.Create;
   Writeln(Test[21][2]);
end.

=== code end ===

Compiles and prints

=== output begin ===

42

=== output end ===

Though one can argue that default array properties are used explicitely
using the "[…]"-syntax while a non-array default property is
automatically triggered with ".".

Regards,
Sven
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Michael Van Canneyt


On Sun, 9 Sep 2018, Sven Barth via fpc-pascal wrote:

> Am 09.09.2018 um 16:16 schrieb Michael Van Canneyt:
>>
>>
>> On Sun, 9 Sep 2018, Ryan Joseph wrote:
>>
>>> It seems syntacticly possible that default properties could be
>>> recursive by having a default property reference a record/class with
>>> another default property. Should that be allowed?
>>
>> I don't think so. Let's start with 1 level.
>> For default arrays it's also only one level deep.
> Ehm... news flash:
I know that, but I meant you cannot immediatly go to the second level.
You must specify the 2 indexes as in:

>   Writeln(Test[21][2]);

> Though one can argue that default array properties are used explicitely
> using the "[…]"-syntax while a non-array default property is
> automatically triggered with ".".

Yes.

Michael.
_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3


> On Sep 10, 2018, at 6:34 AM, Michael Van Canneyt <[hidden email]> wrote:
>
> I know that, but I meant you cannot immediatly go to the second level. You must specify the 2 indexes as in:
>
>>   Writeln(Test[21][2]);

The array property is a little different because it has the [] syntax which is effectively the name. There may be some interesting things to do with “default” being recursive but I’ll just comment it out for now.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
How should this syntax work? TWrapper needs to allow assignments of TWrapper for constructors but it also should accept assignments of THelperA for the default write property.

Is that a problem or should TWrapper be able to assign both? It looks strange so I thought I would ask.

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

type
        THelperA = class
        end;

type
        TWrapper = class
                objA: THelperA;
                property helperA: THelperA read objA write objA; default;
        end;

var
        wrapper: TWrapper;
begin
        wrapper := TWrapper.Create;
        wrapper := THelperA.Create;
end.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3
Is a + operator that returns something other than the record valid?

I tried doing that but I get an error.

type
        TWrapper = record
                class operator + (left: TWrapper; right: integer): integer;
        end;

var
        wrapper: TWrapper;
        i: integer;
begin
        i := wrapper + 1;  // ERROR: "TWrapper" expected “LongInt”

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Benito van der Zander
In reply to this post by Ryan Joseph-3
Hi,

perhaps everything would be clearer, if the default property was accessed with ^  ?


var
	wrapper: TWrapper;
begin
	wrapper := TWrapper.Create;
	wrapper^ := THelperA.Create;
end.

Cheers,
Benito 



Am 14.09.2018 um 10:50 schrieb Ryan Joseph:
How should this syntax work? TWrapper needs to allow assignments of TWrapper for constructors but it also should accept assignments of THelperA for the default write property.

Is that a problem or should TWrapper be able to assign both? It looks strange so I thought I would ask.

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

type
	THelperA = class
	end;

type
	TWrapper = class
		objA: THelperA;
		property helperA: THelperA read objA write objA; default;
	end;

var
	wrapper: TWrapper;
begin
	wrapper := TWrapper.Create;
	wrapper := THelperA.Create;
end.

Regards,
	Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal


_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Reply | Threaded
Open this post in threaded view
|

Re: with in classes/records

Ryan Joseph-3


> On Sep 26, 2018, at 1:14 AM, Benito van der Zander <[hidden email]> wrote:
>
> Hi,
>
> perhaps everything would be clearer, if the default property was accessed with ^  ?
>
>
> var
> wrapper: TWrapper;
> begin
> wrapper := TWrapper.Create;
> wrapper^ := THelperA.Create;
> end.
>

Sorry, back to working on this today.

That’s an idea. Probably the other way around though since initializing the class is the less common operation. The compiler team is busy I guess because I haven’t got any answers recently on questions. I suspect appropriating the ^ symbol will not be ok with them since it has another reserved meaning.

Regards,
        Ryan Joseph

_______________________________________________
fpc-pascal maillist  -  [hidden email]
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
123