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