www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - ref returns and properties

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
I realized that properties are not a complete model for actual data. The 
current conventional wisdom about properties goes like this (T is some 
type):

class Host
{
     property prop
     {
         T get() { ... }
         void set(T value) { ... }
     }
}

If T is e.g. an int, it all works nicely. Now consider T is a 
highly-structured piece of data that holds resources. In that case we 
want to make sure T is not copied unwittingly such that the resource is 
managed properly. This means that T has a copy constructor and a destructor.

For such cases, extensive experience with C++ has shown that two 
primitives are essential: move and swap.

void move(ref T src, ref T dst);
void swap(ref T lhs, ref T rhs);

Move takes the guts of src, puts them in dst, and then clears src 
effectively relieving it from any resource. Swap exchanges the guts of 
src and dst without any extra resource copying.

Now if "prop" were a classic data, swapping host1.prop and host2.prop is 
a piece of cake (that is realized inside std.algorithm. Look it up, the 
implementation has quite a few interesting quirks!)

But if "prop" is a property with get and set, everything falls apart. 
Properties effectively hide the address of the actual data and only 
traffic in values. That's often good (and sometimes even the only 
possibility in the case of properties computed on-the-fly), but this 
abstraction effectively makes resource-conserving move and swap impossible.

I noticed this problem when dealing with ranges. The .head() function, 
if it returns a value, cannot be swapped/moved. Same applies to opIndex 
when implemented as a value property (via the opIndex/opIndexAssign tandem).

What to do? I'd like to refine the notion of property such that moving 
properties around is possible, without, however, complicating their 
interface too much.


Andrei
Jan 25 2009
next sibling parent reply Michiel Helvensteijn <nomail please.com> writes:
Andrei Alexandrescu wrote:

 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);
 
 ...
 
 But if "prop" is a property with get and set, everything falls apart.
 Properties effectively hide the address of the actual data and only
 traffic in values. That's often good (and sometimes even the only
 possibility in the case of properties computed on-the-fly), but this
 abstraction effectively makes resource-conserving move and swap
 impossible.
 
 ...
 
 What to do? I'd like to refine the notion of property such that moving
 properties around is possible, without, however, complicating their
 interface too much.
I posted a possible solution to this (I think) in the last properties thread. I was thinking that properties might be equiped with extra member functions. One advantage is that you might get more efficiency. I think you just named another. class Host {      property prop      {          T get() { ... }          void set(T value) { ... } void move(ref T dst) { ... } void swap(ref T rhs) { ... }      } } Of course, in my idea (and my programming language), there is no difference between f(a, b) and a.f(b), so that automatically works out. Not sure how that would work out in D. One possibility is to just live with Host.prop.move(dst). -- Michiel
Jan 25 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Michiel Helvensteijn wrote:
 Andrei Alexandrescu wrote:
 
 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);

 ...

 But if "prop" is a property with get and set, everything falls apart.
 Properties effectively hide the address of the actual data and only
 traffic in values. That's often good (and sometimes even the only
 possibility in the case of properties computed on-the-fly), but this
 abstraction effectively makes resource-conserving move and swap
 impossible.

 ...

 What to do? I'd like to refine the notion of property such that moving
 properties around is possible, without, however, complicating their
 interface too much.
I posted a possible solution to this (I think) in the last properties thread. I was thinking that properties might be equiped with extra member functions. One advantage is that you might get more efficiency. I think you just named another. class Host { property prop { T get() { ... } void set(T value) { ... } void move(ref T dst) { ... } void swap(ref T rhs) { ... } } }
But these are too many. These should suffice: class Host { property prop { T get(); void acquire(ref T value) { ... } void release(ref T value) { ... } } } set() can be implemented as acquire from a copy, and swap can be implemented by calls to acquire and release. Technically, get() could be also implemented with acquire and release, but that's too intensive for most types. The problem I'm seeing is that most people won't want to go through the trouble of implementing three functions for one property. Andrei
Jan 25 2009
next sibling parent reply Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Sun, Jan 25, 2009 at 3:00 PM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 But these are too many. These should suffice:

 class Host
 {
     property prop
     {
         T get();
         void acquire(ref T value) { ... }
         void release(ref T value) { ... }
     }
 }

 set() can be implemented as acquire from a copy, and swap can be implemented
 by calls to acquire and release. Technically, get() could be also
 implemented with acquire and release, but that's too intensive for most
 types.

 The problem I'm seeing is that most people won't want to go through the
 trouble of implementing three functions for one property.
I absolutely wouldn't. Once we start doing stuff like that, we start getting into tedious C++ territory. Holding the compiler's hand, writing mountains of tedious, easy-to-mess-up boilerplate code, just to be able to implement simple things so they fit in with the language's weird semantics. At that point I'd _rather_ use the current "properties."
Jan 25 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jarrett Billingsley wrote:
 On Sun, Jan 25, 2009 at 3:00 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 But these are too many. These should suffice:

 class Host
 {
     property prop
     {
         T get();
         void acquire(ref T value) { ... }
         void release(ref T value) { ... }
     }
 }

 set() can be implemented as acquire from a copy, and swap can be implemented
 by calls to acquire and release. Technically, get() could be also
 implemented with acquire and release, but that's too intensive for most
 types.

 The problem I'm seeing is that most people won't want to go through the
 trouble of implementing three functions for one property.
I absolutely wouldn't. Once we start doing stuff like that, we start getting into tedious C++ territory. Holding the compiler's hand, writing mountains of tedious, easy-to-mess-up boilerplate code, just to be able to implement simple things so they fit in with the language's weird semantics. At that point I'd _rather_ use the current "properties."
But you lose correctness and efficiency. I agree that in certain cases they aren't paramount, but you can't build a language to not allow them. A possible solution would be to require only get, make acquire necessary only for writable properties, and make release entirely optional. Andrei
Jan 25 2009
next sibling parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Mon, 26 Jan 2009 00:59:32 +0300, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:

 Jarrett Billingsley wrote:
 On Sun, Jan 25, 2009 at 3:00 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 But these are too many. These should suffice:

 class Host
 {
     property prop
     {
         T get();
         void acquire(ref T value) { ... }
         void release(ref T value) { ... }
     }
 }

 set() can be implemented as acquire from a copy, and swap can be  
 implemented
 by calls to acquire and release. Technically, get() could be also
 implemented with acquire and release, but that's too intensive for most
 types.

 The problem I'm seeing is that most people won't want to go through the
 trouble of implementing three functions for one property.
I absolutely wouldn't. Once we start doing stuff like that, we start getting into tedious C++ territory. Holding the compiler's hand, writing mountains of tedious, easy-to-mess-up boilerplate code, just to be able to implement simple things so they fit in with the language's weird semantics. At that point I'd _rather_ use the current "properties."
But you lose correctness and efficiency. I agree that in certain cases they aren't paramount, but you can't build a language to not allow them. A possible solution would be to require only get, make acquire necessary only for writable properties, and make release entirely optional. Andrei
get/set/free?
Jan 25 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Denis Koroskin wrote:
 On Mon, 26 Jan 2009 00:59:32 +0300, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 Jarrett Billingsley wrote:
 On Sun, Jan 25, 2009 at 3:00 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 But these are too many. These should suffice:

 class Host
 {
     property prop
     {
         T get();
         void acquire(ref T value) { ... }
         void release(ref T value) { ... }
     }
 }

 set() can be implemented as acquire from a copy, and swap can be 
 implemented
 by calls to acquire and release. Technically, get() could be also
 implemented with acquire and release, but that's too intensive for most
 types.

 The problem I'm seeing is that most people won't want to go through the
 trouble of implementing three functions for one property.
I absolutely wouldn't. Once we start doing stuff like that, we start getting into tedious C++ territory. Holding the compiler's hand, writing mountains of tedious, easy-to-mess-up boilerplate code, just to be able to implement simple things so they fit in with the language's weird semantics. At that point I'd _rather_ use the current "properties."
But you lose correctness and efficiency. I agree that in certain cases they aren't paramount, but you can't build a language to not allow them. A possible solution would be to require only get, make acquire necessary only for writable properties, and make release entirely optional. Andrei
get/set/free?
With these you can't move a resource inside the property. Andrei
Jan 25 2009
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 [snip]

 get/set/free?
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful? -- Daniel
Jan 25 2009
parent reply Sean Kelly <sean invisibleduck.org> writes:
Daniel Keep wrote:
 Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 [snip]

 get/set/free?
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful?
Does Python have complex value types? Sean
Jan 26 2009
parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
Sean Kelly wrote:
 Daniel Keep wrote:
 Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 [snip]

 get/set/free?
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful?
Does Python have complex value types? Sean
You mean these?
 (1+2j) * (2+3j)
(-4+7j) Don't ask me why they used 'j' instead of 'i'. :P If you mean aggregate types that have value semantics, then no. -- Daniel
Jan 26 2009
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Daniel Keep wrote:
 
 Sean Kelly wrote:
 Daniel Keep wrote:
 Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 [snip]

 get/set/free?
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful?
Does Python have complex value types? Sean
You mean these?
 (1+2j) * (2+3j)
(-4+7j) Don't ask me why they used 'j' instead of 'i'. :P If you mean aggregate types that have value semantics, then no.
He meant the latter (emphasis on the other syllable, I always forget which is which). This brings back the notion of precision and efficiency. C++ tries to define operations such that some never throw, as those can be used in code with transactional semantics. It also tries to manage resources deterministically, which means there's strict control over values being created and destroyed. It's hard to combine these two within a harmonious system. Anyhow, here's a simple D example. Consider we define a BigInt type as a value-type struct: when you copy a BigInt to another, the latter becomes an independent copy: BigInt a = 100; BigInt b = a; ++b; assert(a == 100); BigInt's copy constructor would allocate memory dynamically, which means it may throw and also that it is inefficient to copy BigInt objects unwittingly. So far, so good. Now say we define some range that iterates over BigInts. If that range chooses to implement head() as a property, then a copy is created whenever you ask for head. The small problem is that that's inefficient. The larger problem is that there is no way to correctly e.g. sort such a range. Sorting hinges on swap, and with properties you can't ever swap without risking to throw. Sort would end up throwing, and not only throwing, but losing state irretrievably while at it. Well that's not a foundation we want to build D on, do we? Andrei
Jan 26 2009
parent reply Sergey Gromov <snake.scaly gmail.com> writes:
Mon, 26 Jan 2009 08:06:05 -0800, Andrei Alexandrescu wrote:

 Anyhow, here's a simple D example. Consider we define a BigInt type as a 
 value-type struct: when you copy a BigInt to another, the latter becomes 
 an independent copy:
 
 BigInt a = 100;
 BigInt b = a;
 ++b;
 assert(a == 100);
 
 BigInt's copy constructor would allocate memory dynamically, which means 
 it may throw and also that it is inefficient to copy BigInt objects 
 unwittingly.
 
 So far, so good. Now say we define some range that iterates over 
 BigInts. If that range chooses to implement head() as a property, then a 
 copy is created whenever you ask for head. The small problem is that 
 that's inefficient. The larger problem is that there is no way to 
 correctly e.g. sort such a range. Sorting hinges on swap, and with 
 properties you can't ever swap without risking to throw. Sort would end 
 up throwing, and not only throwing, but losing state irretrievably while 
 at it. Well that's not a foundation we want to build D on, do we?
This will happen in C++, too, if operator*() decides to throw. Algorithms are correct only if objects they manipulate obey the rules. It seems like your rule is, 'forward ranges are nothrow.' Any senior ranges included. Or maybe throwing next() or empty() are less dangerous?
Jan 26 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Sergey Gromov wrote:
 Mon, 26 Jan 2009 08:06:05 -0800, Andrei Alexandrescu wrote:
 
 Anyhow, here's a simple D example. Consider we define a BigInt type as a 
 value-type struct: when you copy a BigInt to another, the latter becomes 
 an independent copy:

 BigInt a = 100;
 BigInt b = a;
 ++b;
 assert(a == 100);

 BigInt's copy constructor would allocate memory dynamically, which means 
 it may throw and also that it is inefficient to copy BigInt objects 
 unwittingly.

 So far, so good. Now say we define some range that iterates over 
 BigInts. If that range chooses to implement head() as a property, then a 
 copy is created whenever you ask for head. The small problem is that 
 that's inefficient. The larger problem is that there is no way to 
 correctly e.g. sort such a range. Sorting hinges on swap, and with 
 properties you can't ever swap without risking to throw. Sort would end 
 up throwing, and not only throwing, but losing state irretrievably while 
 at it. Well that's not a foundation we want to build D on, do we?
This will happen in C++, too, if operator*() decides to throw. Algorithms are correct only if objects they manipulate obey the rules.
Of course. The problem with get and set is not that they make it possible to write incorrect code. They make it impossible to write correct code.
 It seems like your rule is, 'forward ranges are nothrow.'  Any senior
 ranges included.  Or maybe throwing next() or empty() are less
 dangerous?
Yes, because even if they throw, net loss of state is still avoidable. Andrei
Jan 26 2009
prev sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Daniel Keep (daniel.keep.lists gmail.com)'s article
 Sean Kelly wrote:
 Daniel Keep wrote:
 Andrei Alexandrescu wrote:
 Denis Koroskin wrote:
 [snip]

 get/set/free?
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful?
Does Python have complex value types? Sean
You mean these?
 (1+2j) * (2+3j)
(-4+7j) Don't ask me why they used 'j' instead of 'i'. :P If you mean aggregate types that have value semantics, then no. -- Daniel
Using j instead of i is pretty common in electrical engineering circles. i means current when dealing with circuits, and complex numbers are used all over the place to make the math easier when doing circuit stuff, so by convention j is used instead of i to denote imaginary numbers.
Jan 26 2009
parent John Reimer <terminal.node gmail.com> writes:
Hello dsimcha,

 == Quote from Daniel Keep (daniel.keep.lists gmail.com)'s article
 
 Sean Kelly wrote:
 
 Daniel Keep wrote:
 
 Andrei Alexandrescu wrote:
 
 Denis Koroskin wrote:
 
 [snip]
 
 get/set/free?
 
With these you can't move a resource inside the property. Andrei
Python has an overload for removing properties. In all my years of using Python, I've *NEVER* once had a use for it, or even worked out why I'd want to use it. I'm not saying we shouldn't be able to do this, I just can't see the need for move/remove for properties; where would this be useful?
Does Python have complex value types? Sean
You mean these?
 (1+2j) * (2+3j)
 
(-4+7j) Don't ask me why they used 'j' instead of 'i'. :P If you mean aggregate types that have value semantics, then no. -- Daniel
Using j instead of i is pretty common in electrical engineering circles. i means current when dealing with circuits, and complex numbers are used all over the place to make the math easier when doing circuit stuff, so by convention j is used instead of i to denote imaginary numbers.
Right, the 'j' notation is used in the representation of phasors in ac circuit analysis. If I remember correctly, 'i' , usually represented as a function of time, is the changing current (like in a sinusoidal waveform)... and 'I' is the instantaneous current. I used to do a lot of phasor calculation practice. :-P -JJR
Jan 26 2009
prev sibling parent reply Jarrett Billingsley <jarrett.billingsley gmail.com> writes:
On Sun, Jan 25, 2009 at 4:59 PM, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org> wrote:
 But you lose correctness and efficiency. I agree that in certain cases they
 aren't paramount, but you can't build a language to not allow them.
Correctness is fine, but efficiency be damned. Look at where _that_ mantra has gotten C++.
Jan 25 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jarrett Billingsley wrote:
 On Sun, Jan 25, 2009 at 4:59 PM, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:
 But you lose correctness and efficiency. I agree that in certain cases they
 aren't paramount, but you can't build a language to not allow them.
Correctness is fine, but efficiency be damned.
You and I don't seem to frequent the same circles.
 Look at where _that_
 mantra has gotten C++.
Not sure that efficiency is the source of most or even many of the known issues with C++. Andrei
Jan 25 2009
prev sibling parent reply Jacob Carlborg <doobnet gmail.com> writes:
Andrei Alexandrescu wrote:
 Michiel Helvensteijn wrote:
 Andrei Alexandrescu wrote:

 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);

 ...

 But if "prop" is a property with get and set, everything falls apart.
 Properties effectively hide the address of the actual data and only
 traffic in values. That's often good (and sometimes even the only
 possibility in the case of properties computed on-the-fly), but this
 abstraction effectively makes resource-conserving move and swap
 impossible.

 ...

 What to do? I'd like to refine the notion of property such that moving
 properties around is possible, without, however, complicating their
 interface too much.
I posted a possible solution to this (I think) in the last properties thread. I was thinking that properties might be equiped with extra member functions. One advantage is that you might get more efficiency. I think you just named another. class Host { property prop { T get() { ... } void set(T value) { ... } void move(ref T dst) { ... } void swap(ref T rhs) { ... } } }
But these are too many. These should suffice: class Host { property prop { T get(); void acquire(ref T value) { ... } void release(ref T value) { ... } } } set() can be implemented as acquire from a copy, and swap can be implemented by calls to acquire and release. Technically, get() could be also implemented with acquire and release, but that's too intensive for most types. The problem I'm seeing is that most people won't want to go through the trouble of implementing three functions for one property. Andrei
What about this: class Host { property acquire release prop { T get(); } } And then the compiler will automatically created the "acquire" and "release" methods. There could also be what I would like to call "property shortcuts" (ruby calls this attributes) like this: class Host { get acquire release T prop; get set T prop2; }
Jan 26 2009
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jacob Carlborg wrote:
[on properties]
 What about this:
 
 class Host
 {
      property acquire release prop
      {
          T get();
      }
 }
 
 And then the compiler will automatically created the "acquire" and 
 "release" methods.
 
 There could also be what I would like to call "property shortcuts" (ruby 
 calls this attributes) like this:
 
 class Host
 {
      get acquire release T prop;
      get set T prop2;
 }
I was hoping I'd shield putative users from having to write verbose code. Besides, there's one other issue I stumbled upon while working on porting std.algorithm to ranges. You can't really pass an entire property to a function. This furthers the rift between properties and true fields. Consider: struct A { int x; } void foo(ref int n) { if (n == 0) n = 1; } ... A obj; foo(obj.x); So when passing obj.x to foo, foo can read and write it no problem. But if x is a property of A, it all falls apart: obj.x means just reading the property, not getting the property with all of its get and set splendor. How did this hit me in std.algorithm? Replace A with your range of choice, x with head, and foo with swap. If head is implemented as a property instead of a ref-returning function or a field, I can't swap the heads of two ranges!! This limits how ranges can be implemented; I was hoping to allow head as a property, but it looks like I can't. Same goes about opIndex. If you define a random-access range, you can't define opIndex and opIndexAssign. You must define opIndex to return a reference. That's a bummer. Andrei
Jan 26 2009
parent reply Don <nospam nospam.com> writes:
Andrei Alexandrescu wrote:
 Jacob Carlborg wrote:
 [on properties]
 What about this:

 class Host
 {
      property acquire release prop
      {
          T get();
      }
 }

 And then the compiler will automatically created the "acquire" and 
 "release" methods.

 There could also be what I would like to call "property shortcuts" 
 (ruby calls this attributes) like this:

 class Host
 {
      get acquire release T prop;
      get set T prop2;
 }
I was hoping I'd shield putative users from having to write verbose code. Besides, there's one other issue I stumbled upon while working on porting std.algorithm to ranges. You can't really pass an entire property to a function. This furthers the rift between properties and true fields. Consider: struct A { int x; } void foo(ref int n) { if (n == 0) n = 1; } ... A obj; foo(obj.x); So when passing obj.x to foo, foo can read and write it no problem. But if x is a property of A, it all falls apart: obj.x means just reading the property, not getting the property with all of its get and set splendor.
I'm starting to wonder if we need some restrictions on fields, in order to make properties and fields interchangable. The idea that a field could eventually be completely replaceable with functions is appealing, but I think it's only possible with a huge performance hit. One can distinguish between POD fields (for which any operation is legal) and property fields (which you cannot take the address of, for example). Arguably classes should normally not contain public POD fields, only public property fields. Perhaps this is a useful concept. Incidentally, a simpler way of bridging the divide would have been to drop property syntax and instead allow public fields to be accessed using function notation. Setting would be similar to C++ constructors: int z = obj.x() + 7; obj.x(6);
 How did this hit me in std.algorithm? Replace A with your range of 
 choice, x with head, and foo with swap. If head is implemented as a 
 property instead of a ref-returning function or a field, I can't swap 
 the heads of two ranges!! This limits how ranges can be implemented; I 
 was hoping to allow head as a property, but it looks like I can't. Same 
 goes about opIndex. If you define a random-access range, you can't 
 define opIndex and opIndexAssign. You must define opIndex to return a 
 reference. That's a bummer.
Yuck.
 
 
 Andrei
Jan 26 2009
parent reply Sean Kelly <sean invisibleduck.org> writes:
Don wrote:
 Andrei Alexandrescu wrote:
 Jacob Carlborg wrote:
 [on properties]
 What about this:

 class Host
 {
      property acquire release prop
      {
          T get();
      }
 }

 And then the compiler will automatically created the "acquire" and 
 "release" methods.

 There could also be what I would like to call "property shortcuts" 
 (ruby calls this attributes) like this:

 class Host
 {
      get acquire release T prop;
      get set T prop2;
 }
I was hoping I'd shield putative users from having to write verbose code. Besides, there's one other issue I stumbled upon while working on porting std.algorithm to ranges. You can't really pass an entire property to a function. This furthers the rift between properties and true fields. Consider: struct A { int x; } void foo(ref int n) { if (n == 0) n = 1; } ... A obj; foo(obj.x); So when passing obj.x to foo, foo can read and write it no problem. But if x is a property of A, it all falls apart: obj.x means just reading the property, not getting the property with all of its get and set splendor.
I'm starting to wonder if we need some restrictions on fields, in order to make properties and fields interchangable. The idea that a field could eventually be completely replaceable with functions is appealing, but I think it's only possible with a huge performance hit. One can distinguish between POD fields (for which any operation is legal) and property fields (which you cannot take the address of, for example). Arguably classes should normally not contain public POD fields, only public property fields. Perhaps this is a useful concept.
I'm hesitant to assert that it should be illegal to take the address of a public data member, but this is certainly an intriguing idea. The only other option I can think of would be to make swap a built-in property of concrete types and expecting the user to call a.swap(b), though it seems weird that a.swap(b) couldn't be rewritten as swap(a,b) for any concrete type, regardless of context. Sean
Jan 26 2009
parent Don <nospam nospam.com> writes:
Sean Kelly wrote:
 Don wrote:
 Andrei Alexandrescu wrote:
 Jacob Carlborg wrote:
 [on properties]
 What about this:

 class Host
 {
      property acquire release prop
      {
          T get();
      }
 }

 And then the compiler will automatically created the "acquire" and 
 "release" methods.

 There could also be what I would like to call "property shortcuts" 
 (ruby calls this attributes) like this:

 class Host
 {
      get acquire release T prop;
      get set T prop2;
 }
I was hoping I'd shield putative users from having to write verbose code. Besides, there's one other issue I stumbled upon while working on porting std.algorithm to ranges. You can't really pass an entire property to a function. This furthers the rift between properties and true fields. Consider: struct A { int x; } void foo(ref int n) { if (n == 0) n = 1; } ... A obj; foo(obj.x); So when passing obj.x to foo, foo can read and write it no problem. But if x is a property of A, it all falls apart: obj.x means just reading the property, not getting the property with all of its get and set splendor.
I'm starting to wonder if we need some restrictions on fields, in order to make properties and fields interchangable. The idea that a field could eventually be completely replaceable with functions is appealing, but I think it's only possible with a huge performance hit. One can distinguish between POD fields (for which any operation is legal) and property fields (which you cannot take the address of, for example). Arguably classes should normally not contain public POD fields, only public property fields. Perhaps this is a useful concept.
I'm hesitant to assert that it should be illegal to take the address of a public data member, but this is certainly an intriguing idea.
At least, it's something you could do in a lint tool. I think it would be a useful warning for classes. It still wouldn't solve Andrei's problem, though, because you still need public fields of structs to be POD fields, and a struct can still have properties. Then there are module properties... The
 only other option I can think of would be to make swap a built-in 
 property of concrete types and expecting the user to call a.swap(b), 
 though it seems weird that a.swap(b) couldn't be rewritten as swap(a,b) 
 for any concrete type, regardless of context.
 
 
 Sean
Jan 27 2009
prev sibling parent Yigal Chripun <yigal100 gmail.com> writes:
Jacob Carlborg wrote:
 Andrei Alexandrescu wrote:
 Michiel Helvensteijn wrote:
 Andrei Alexandrescu wrote:

 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);

 ...

 But if "prop" is a property with get and set, everything falls apart.
 Properties effectively hide the address of the actual data and only
 traffic in values. That's often good (and sometimes even the only
 possibility in the case of properties computed on-the-fly), but this
 abstraction effectively makes resource-conserving move and swap
 impossible.

 ...

 What to do? I'd like to refine the notion of property such that moving
 properties around is possible, without, however, complicating their
 interface too much.
I posted a possible solution to this (I think) in the last properties thread. I was thinking that properties might be equiped with extra member functions. One advantage is that you might get more efficiency. I think you just named another. class Host { property prop { T get() { ... } void set(T value) { ... } void move(ref T dst) { ... } void swap(ref T rhs) { ... } } }
But these are too many. These should suffice: class Host { property prop { T get(); void acquire(ref T value) { ... } void release(ref T value) { ... } } } set() can be implemented as acquire from a copy, and swap can be implemented by calls to acquire and release. Technically, get() could be also implemented with acquire and release, but that's too intensive for most types. The problem I'm seeing is that most people won't want to go through the trouble of implementing three functions for one property. Andrei
What about this: class Host { property acquire release prop { T get(); } } And then the compiler will automatically created the "acquire" and "release" methods. There could also be what I would like to call "property shortcuts" (ruby calls this attributes) like this: class Host { get acquire release T prop; get set T prop2; }
I agree with Daniel and I think that 99.999% of users will use properties with just get/set methods and those should be the methods for defining properties, _not_ acquire/release as above. the other 0.001% can just define extra functions in the body of the property. The design should be geared towards the common and simple case.
Jan 26 2009
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
I realized that properties are not a complete model for actual data. The 
current conventional wisdom about properties goes like this (T is some 
type):

 class Host
 {
     property prop
     {
         T get() { ... }
         void set(T value) { ... }
     }
 }

 If T is e.g. an int, it all works nicely. Now consider T is a 
 highly-structured piece of data that holds resources. In that case we want 
 to make sure T is not copied unwittingly such that the resource is managed 
 properly. This means that T has a copy constructor and a destructor.

 For such cases, extensive experience with C++ has shown that two 
 primitives are essential: move and swap.

 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);

 Move takes the guts of src, puts them in dst, and then clears src 
 effectively relieving it from any resource. Swap exchanges the guts of src 
 and dst without any extra resource copying.

 Now if "prop" were a classic data, swapping host1.prop and host2.prop is a 
 piece of cake (that is realized inside std.algorithm. Look it up, the 
 implementation has quite a few interesting quirks!)

 But if "prop" is a property with get and set, everything falls apart. 
 Properties effectively hide the address of the actual data and only 
 traffic in values. That's often good (and sometimes even the only 
 possibility in the case of properties computed on-the-fly), but this 
 abstraction effectively makes resource-conserving move and swap 
 impossible.

 I noticed this problem when dealing with ranges. The .head() function, if 
 it returns a value, cannot be swapped/moved. Same applies to opIndex when 
 implemented as a value property (via the opIndex/opIndexAssign tandem).

 What to do? I'd like to refine the notion of property such that moving 
 properties around is possible, without, however, complicating their 
 interface too much.
There are two possibilities that I can think of. 1. ref return from get, which gets you part-way there. You can still do some useful things like which member you are returning getting calculated, but it doesn't make the call to set when you assign to the property, or allow calling get multiple times to return different values. 2. Have a "property delegate", which is like a normal delegate, but contains 2 functions. Calling a function which takes such a delegate makes the compiler generate such a delegate in the case where a simple lvalue is passed. You could signify it with the terms "lazy ref" in the function signature. option 1 is the simple solution, and covers some ground. I don't think it's universally useful, but it could provide a mechanism for things like head() to work. option 2 solves the problem, but introduces some complex, possibly performance hurting mechanisms. The way to solve this last part is probably to allow specification of multiple functions, and have the compiler choose the non 'lazy-ref' version if it is passed a simple lvalue. class C { private int _x; private int _y; this(int n) { _x = n; } property prop { int get() { return _y; } void set(int x) { _y += _x * x; } // some weird semantics for set } } void foo(lazy ref z) { z += 100; } translates to: struct property_delegate(T) { void *ptr; T function(void*) get; void function(void *, T) set; } void foo(property_delegate!(int) pd) { pd.set(pd.ptr, pd.get(pd.ptr) + 100); } usage: void bar() { auto c = new C(2); /* generates a property delegate with prop.get and prop.set as the functions, and c as the pointer */ foo(c.prop); assert(c.prop == 200); int n = c.prop; /* generates a property delegate with get and set functions being generated inner functions of bar which set and get n, and the stack frame as the pointer. */ foo(n); assert(n == 300); } -Steve
Jan 26 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 "Andrei Alexandrescu" wrote
 I realized that properties are not a complete model for actual data. The 
 current conventional wisdom about properties goes like this (T is some 
 type):

 class Host
 {
     property prop
     {
         T get() { ... }
         void set(T value) { ... }
     }
 }

 If T is e.g. an int, it all works nicely. Now consider T is a 
 highly-structured piece of data that holds resources. In that case we want 
 to make sure T is not copied unwittingly such that the resource is managed 
 properly. This means that T has a copy constructor and a destructor.

 For such cases, extensive experience with C++ has shown that two 
 primitives are essential: move and swap.

 void move(ref T src, ref T dst);
 void swap(ref T lhs, ref T rhs);

 Move takes the guts of src, puts them in dst, and then clears src 
 effectively relieving it from any resource. Swap exchanges the guts of src 
 and dst without any extra resource copying.

 Now if "prop" were a classic data, swapping host1.prop and host2.prop is a 
 piece of cake (that is realized inside std.algorithm. Look it up, the 
 implementation has quite a few interesting quirks!)

 But if "prop" is a property with get and set, everything falls apart. 
 Properties effectively hide the address of the actual data and only 
 traffic in values. That's often good (and sometimes even the only 
 possibility in the case of properties computed on-the-fly), but this 
 abstraction effectively makes resource-conserving move and swap 
 impossible.

 I noticed this problem when dealing with ranges. The .head() function, if 
 it returns a value, cannot be swapped/moved. Same applies to opIndex when 
 implemented as a value property (via the opIndex/opIndexAssign tandem).

 What to do? I'd like to refine the notion of property such that moving 
 properties around is possible, without, however, complicating their 
 interface too much.
There are two possibilities that I can think of. 1. ref return from get, which gets you part-way there. You can still do some useful things like which member you are returning getting calculated, but it doesn't make the call to set when you assign to the property, or allow calling get multiple times to return different values.
Yah, exactly. One thing I'm rather sad about is that certain containers won't be implementable that way, for example sparse vectors (with most elements zero). A sparse vector would want to return by value from head() and opIndex() (so it can return 0.0 most of the time) and would also want to have control over setting slots. So in brief, sparse vectors won't be supported naturally by our paradigm. They will still be supported, just not in a manner that makes them next to indistinguishable from dense vectors. Lately I'm getting to think (sour grapes syndrome?) that probably that's a good thing. A sparse vector is very long - e.g. millions of items, and only has a few or a few hundred that are nonzero. It would be wasteful to ever iterate over the whole one million items; I'd rather use a custom algorithm that only "sees" the nonzero elements. It would be nice to have opApply work, but I guess I can live with that too.
 2. Have a "property delegate", which is like a normal delegate, but contains 
 2 functions.  Calling a function which takes such a delegate makes the 
 compiler generate such a delegate in the case where a simple lvalue is 
 passed.  You could signify it with the terms "lazy ref" in the function 
 signature.
Yah, crossed my mind too. With the third function in tow (the one that does the resource acquisition or whatnot) things are getting rather overweight and it starts looking like a full-fledged local class with a vtable and the works might be an option. Local classes have access to the frame pointer of their parent, so probably things can be arranged to work. But the cost of the whole operation becomes too high. Andrei
Jan 26 2009
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 There are two possibilities that I can think of.

 1. ref return from get, which gets you part-way there.  You can still do 
 some useful things like which member you are returning getting 
 calculated, but it doesn't make the call to set when you assign to the 
 property, or allow calling get multiple times to return different values.
Yah, exactly. One thing I'm rather sad about is that certain containers won't be implementable that way, for example sparse vectors (with most elements zero). A sparse vector would want to return by value from head() and opIndex() (so it can return 0.0 most of the time) and would also want to have control over setting slots. So in brief, sparse vectors won't be supported naturally by our paradigm. They will still be supported, just not in a manner that makes them next to indistinguishable from dense vectors. Lately I'm getting to think (sour grapes syndrome?) that probably that's a good thing. A sparse vector is very long - e.g. millions of items, and only has a few or a few hundred that are nonzero. It would be wasteful to ever iterate over the whole one million items; I'd rather use a custom algorithm that only "sees" the nonzero elements. It would be nice to have opApply work, but I guess I can live with that too.
A good point. Let's not forget that the main purpose of ranges being special is to support foreach. Once you start using ranges in different ways, then it becomes more of a generalized structure, up to the library designer as to how to solve these problems.
 2. Have a "property delegate", which is like a normal delegate, but 
 contains 2 functions.  Calling a function which takes such a delegate 
 makes the compiler generate such a delegate in the case where a simple 
 lvalue is passed.  You could signify it with the terms "lazy ref" in the 
 function signature.
Yah, crossed my mind too. With the third function in tow (the one that does the resource acquisition or whatnot) things are getting rather overweight and it starts looking like a full-fledged local class with a vtable and the works might be an option. Local classes have access to the frame pointer of their parent, so probably things can be arranged to work. But the cost of the whole operation becomes too high.
I guess I'm not really sure what the "acquire" method does. I saw you mention it in another post, but with no explanation as to what it actually does, just that it was needed. I'm sure I'm not getting what should be obvious, but if you could enlighten, I would appreciate it :) Having the two functions seems like a reasonable baggage compromise, if you want to control the get and set methods of a property and pass that control to an underlying function, you need at least that. If we start generalizing it to anything, then it starts looking like struct interfaces would be more suitable. For example, you could allow setting an int property using both an int and a string, do you want to pass both setters to a function? I think limiting it to just a set and a get function should be sufficient. -Steve
Jan 26 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 I guess I'm not really sure what the "acquire" method does.  I saw you 
 mention it in another post, but with no explanation as to what it actually 
 does, just that it was needed.  I'm sure I'm not getting what should be 
 obvious, but if you could enlighten, I would appreciate it :)
Say you have an object obj that has a Matrix property and you have a Matrix object handy. The Matrix is rather resource intensive so you'd rather not copy it unwittingly. Conventionally, if you say: Matrix m; ... fill matrix ... obj.matrix = m; then m is copied into obj.matrix (there is the by-value call to set property). Now it's possible to arrange things such that m is destructively copied, but as the auto_ptr disaster has shown, it's not that intuitive to make assignment destroy the right hand side. So we'd need a distinct method: obj.matrix.acquire(m); That method takes the matrix by reference, sucks its life out of it, and leaves an empty shell behind. Pretty much like in your average horror movie.
 Having the two functions seems like a reasonable baggage compromise, if you 
 want to control the get and set methods of a property and pass that control 
 to an underlying function, you need at least that.  If we start generalizing 
 it to anything, then it starts looking like struct interfaces would be more 
 suitable.  For example, you could allow setting an int property using both 
 an int and a string, do you want to pass both setters to a function?  I 
 think limiting it to just a set and a get function should be sufficient.
With only get and set I can't implement a nothrow swap, which kinda bends me out of shape. Andrei
Jan 26 2009
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I guess I'm not really sure what the "acquire" method does.  I saw you 
 mention it in another post, but with no explanation as to what it 
 actually does, just that it was needed.  I'm sure I'm not getting what 
 should be obvious, but if you could enlighten, I would appreciate it :)
Say you have an object obj that has a Matrix property and you have a Matrix object handy. The Matrix is rather resource intensive so you'd rather not copy it unwittingly. Conventionally, if you say: Matrix m; ... fill matrix ... obj.matrix = m; then m is copied into obj.matrix (there is the by-value call to set property). Now it's possible to arrange things such that m is destructively copied, but as the auto_ptr disaster has shown, it's not that intuitive to make assignment destroy the right hand side. So we'd need a distinct method: obj.matrix.acquire(m); That method takes the matrix by reference, sucks its life out of it, and leaves an empty shell behind. Pretty much like in your average horror movie.
So what you are saying is that you want to control the set such that it destroys the source (essentially, I'm reading, you don't want to do a deep copy, just a reference copy, and then null the original reference). Let me ask another question then... does a property have reason to have both set AND acquire? More specifically, does a function that takes a property as an argument as we have been discussing need both set and acquire passed to it? If not, you can simply pass the set method that you want, still only requiring a pair of functions. How to signal that to the compiler easily? Not so sure... but it does seem to me like a type should know how it should be copied, and likely doing it other ways should require special methods. I can think of possible library solutions to this, say a wrapper property struct that can call any two functions to do set or get.
 Having the two functions seems like a reasonable baggage compromise, if 
 you want to control the get and set methods of a property and pass that 
 control to an underlying function, you need at least that.  If we start 
 generalizing it to anything, then it starts looking like struct 
 interfaces would be more suitable.  For example, you could allow setting 
 an int property using both an int and a string, do you want to pass both 
 setters to a function?  I think limiting it to just a set and a get 
 function should be sufficient.
With only get and set I can't implement a nothrow swap, which kinda bends me out of shape.
Sorry, don't get that either. Why can't set and get be used in a nothrow swap? -Steve
Jan 26 2009
parent reply "Denis Koroskin" <2korden gmail.com> writes:
On Tue, 27 Jan 2009 01:52:26 +0300, Steven Schveighoffer <schveiguy yahoo.com>
wrote:

 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I guess I'm not really sure what the "acquire" method does.  I saw you
 mention it in another post, but with no explanation as to what it
 actually does, just that it was needed.  I'm sure I'm not getting what
 should be obvious, but if you could enlighten, I would appreciate it :)
Say you have an object obj that has a Matrix property and you have a Matrix object handy. The Matrix is rather resource intensive so you'd rather not copy it unwittingly. Conventionally, if you say: Matrix m; ... fill matrix ... obj.matrix = m; then m is copied into obj.matrix (there is the by-value call to set property). Now it's possible to arrange things such that m is destructively copied, but as the auto_ptr disaster has shown, it's not that intuitive to make assignment destroy the right hand side. So we'd need a distinct method: obj.matrix.acquire(m); That method takes the matrix by reference, sucks its life out of it, and leaves an empty shell behind. Pretty much like in your average horror movie.
So what you are saying is that you want to control the set such that it destroys the source (essentially, I'm reading, you don't want to do a deep copy, just a reference copy, and then null the original reference). Let me ask another question then... does a property have reason to have both set AND acquire? More specifically, does a function that takes a property as an argument as we have been discussing need both set and acquire passed to it? If not, you can simply pass the set method that you want, still only requiring a pair of functions. How to signal that to the compiler easily? Not so sure... but it does seem to me like a type should know how it should be copied, and likely doing it other ways should require special methods. I can think of possible library solutions to this, say a wrapper property struct that can call any two functions to do set or get.
 Having the two functions seems like a reasonable baggage compromise, if
 you want to control the get and set methods of a property and pass that
 control to an underlying function, you need at least that.  If we start
 generalizing it to anything, then it starts looking like struct
 interfaces would be more suitable.  For example, you could allow  
 setting
 an int property using both an int and a string, do you want to pass  
 both
 setters to a function?  I think limiting it to just a set and a get
 function should be sufficient.
With only get and set I can't implement a nothrow swap, which kinda bends me out of shape.
Sorry, don't get that either. Why can't set and get be used in a nothrow swap? -Steve
Because set implies data copying (read: allocation) and copy constructor call, both of which can throw. How about the following - you call a swap with a dot syntax: int x = 42; int y = 13; x.swap(y); // calls global void swap(T)(ref T, ref T); or a specialized version, swap(ref int, ref int); User is, however, free to implement his own swap method: struct BigInt { ... void swap(ref BigInt other) { ... } ... } Or another way - "void swap(T)(ref T lhs, ref T rhs);" tries "lhs.swap(rhs);" first, and falls back to "auto tmp = lhs; lhs = rhs; rhs = tmp;" if no lhs.swap method is defined. This way you can define "void aquire(ref T newOwner, ref T data);" as "newOwner.aquire(data);" and fall back to "swap(newOwner, data); release(data);" on failure. "void release(ref T obj);" tries "obj.release();" first and falls back to "swap(obj, T());" or "obj = T();" on failure.
Jan 26 2009
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
"Denis Koroskin" wrote
 On Tue, 27 Jan 2009 01:52:26 +0300, Steven Schveighoffer 
 <schveiguy yahoo.com> wrote:

 "Andrei Alexandrescu" wrote
 Steven Schveighoffer wrote:
 I guess I'm not really sure what the "acquire" method does.  I saw you
 mention it in another post, but with no explanation as to what it
 actually does, just that it was needed.  I'm sure I'm not getting what
 should be obvious, but if you could enlighten, I would appreciate it :)
Say you have an object obj that has a Matrix property and you have a Matrix object handy. The Matrix is rather resource intensive so you'd rather not copy it unwittingly. Conventionally, if you say: Matrix m; ... fill matrix ... obj.matrix = m; then m is copied into obj.matrix (there is the by-value call to set property). Now it's possible to arrange things such that m is destructively copied, but as the auto_ptr disaster has shown, it's not that intuitive to make assignment destroy the right hand side. So we'd need a distinct method: obj.matrix.acquire(m); That method takes the matrix by reference, sucks its life out of it, and leaves an empty shell behind. Pretty much like in your average horror movie.
So what you are saying is that you want to control the set such that it destroys the source (essentially, I'm reading, you don't want to do a deep copy, just a reference copy, and then null the original reference). Let me ask another question then... does a property have reason to have both set AND acquire? More specifically, does a function that takes a property as an argument as we have been discussing need both set and acquire passed to it? If not, you can simply pass the set method that you want, still only requiring a pair of functions. How to signal that to the compiler easily? Not so sure... but it does seem to me like a type should know how it should be copied, and likely doing it other ways should require special methods. I can think of possible library solutions to this, say a wrapper property struct that can call any two functions to do set or get.
 Having the two functions seems like a reasonable baggage compromise, if
 you want to control the get and set methods of a property and pass that
 control to an underlying function, you need at least that.  If we start
 generalizing it to anything, then it starts looking like struct
 interfaces would be more suitable.  For example, you could allow 
 setting
 an int property using both an int and a string, do you want to pass 
 both
 setters to a function?  I think limiting it to just a set and a get
 function should be sufficient.
With only get and set I can't implement a nothrow swap, which kinda bends me out of shape.
Sorry, don't get that either. Why can't set and get be used in a nothrow swap? -Steve
Because set implies data copying (read: allocation) and copy constructor call, both of which can throw. How about the following - you call a swap with a dot syntax: int x = 42; int y = 13; x.swap(y); // calls global void swap(T)(ref T, ref T); or a specialized version, swap(ref int, ref int); User is, however, free to implement his own swap method: struct BigInt { ... void swap(ref BigInt other) { ... } ... } Or another way - "void swap(T)(ref T lhs, ref T rhs);" tries "lhs.swap(rhs);" first, and falls back to "auto tmp = lhs; lhs = rhs; rhs = tmp;" if no lhs.swap method is defined. This way you can define "void aquire(ref T newOwner, ref T data);" as "newOwner.aquire(data);" and fall back to "swap(newOwner, data); release(data);" on failure. "void release(ref T obj);" tries "obj.release();" first and falls back to "swap(obj, T());" or "obj = T();" on failure.
OK, I think I got what you and Andrei are saying. I had to think about this a while, and I think Denis is right. Leave the swap implementation up to the type, not the holder of the type. A property should be a simple interface to define how to access an object's data, which is the most common case. Putting in all these extra methods and features to allow the possibility of a non-specific swap function for certain uncommon cases seems like you are trying to remove complexity from an opaque low level function and adding the complexity to all properties in existance instead. i.e. if you want to sort an array of matrices which are expensive to make copies of, then you need a special swap, you can't use the generic one, because it's not a generic swap. Let the type tell you how to do it or let the developer pass in a swap function. Don't put the intelligence into the holder of the type(which in this case would be a range). For simple types, a complex swap or property interface isn't necessary. I hope that was clear, it's hard to express what I'm trying to say... -Steve
Jan 26 2009