www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Fixing D's Properties

reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
Properties are a blemish of the D language.  In the first part of this 
post, I'll explain why and try to show how these properties are /not/ 
good or even better than properties in other languages.  Then I'll 
suggest some possible solutions.  I don't care if the solutions I 
suggest are used, but I will be very thankful if this gets fixed somehow.

The first part of this is really for anyone who believes that D's 
properties are just dandy.  So if you've already been bitten by these, 
feel free to skip down to the solution part of the post.

I suppose I should first give some credit to a couple of the strengths 
of D properties:
- They do not need any keywords.
- They can be used outside of classes - pretty much anywhere functions 
can be used.
When I offer a solution, I'll try to make one that preserves these 
advantages.

The following examples are written for D v1.xxx.
They are not tested on D v2.xxx, but may still work.

Here is a list of some of their shortcomings:

-------------------------------------(1)

First, the very common ambiguity:

Does
foo.bar++;
become
foo.bar( foo.bar + 1 );
or
foo.bar() + 1;
??

This is why we can't write things like this:

ubyte[] array = new ubyte[50];
//...
array.length++;
//...
array.length += 10;
// Note how builtin types use properties heavily.

Which means a large set of our beloved shortcut operators are completely 
broken on properties.

-------------------------------------(2)

Second, while the above is problematic, it creates yet another problem!

migration from member fields to properties.
Suppose a programmer have a class like so:

class SomeContainer
{
   int data;
}

Someone else using SomeContainer might write code like so:
auto s = new SomeContainer;
s.data++;

Now the programmer does a slight modification to SomeContainer, one that 
he does not expect to break reverse compatibility:

class SomeContainer
{
   private int m_data;
   int data() { return m_data; }
   void data( int value ) { m_data = value; }
}

Now that someone else has a problem, because their code no longer compiles,
since s.data++; is no longer a valid expression.

The ability to write a lightweight interface using fields, and then at 
will promote it to a heavyweight interface of properties, is extremely 
handy.

-------------------------------------(3)

Third, there is a problem with value types and properties.
Consider the following code:

class Widget
{
   Rectangle rect() { return m_rect; }
   void rect( Rectangle value ) { m_rect = value; }

   private Rectangle m_rect;
}

struct Rectangle
{
   int x, y, w, h;

   // just a basic opCall "constructor", nothing important
   static Rectangle opCall( int x, int y, int w, int h )
   {
     Rectangle result;
     result.x = x; result.y = y;
     result.w = w; result.h = h;
     return result;
   }
}

void main()
{
   Widget w = new Widget();

   // Insert some default rectangle.
   // The private field is assigned to so that you can remove the write 
property
   //   and see that the line with w.rect.x = 10; will still compile, even
   //   without a write property!
   w.m_rect = Rectangle( 0, 0, 80, 20 );

   // now comes the fun part...
   w.rect.x = 10;
   assert( w.rect.x == 10 ); // fails
}

That code seems to be trying to write 10 to w's rect's x field.  But it 
doesn't.
It does this instead:
//----
Rect temp = w.rect;
temp.x = 10;
//----
It never writes the temporary back into the widget's field as expected.

Also, try doing as the comment in the example suggests:  uncomment the 
write property, then compile.
Observe how it still compiles, seemingly allowing you to write to a 
property that doesn't exist!

-------------------------------------(4)

Fourth, delegates get the shaft.
Consider this:

class Widget
{
   private void delegate() m_onClick; // called when a click happens.

   void delegate() onClick() { return m_onClick; }
   void onClick( void delegate() value ) { m_onClick = value; }

   // code that polls input and calls onClick() is absent from this 
example.
}

void main()
{
   void handler()
   {
     // handles onClick()
     printf( "Click happened!" );
     // please forgive the use of printf, I don't know whether you are using
     //   Tango or Phobos.
   }

   Widget w = new Widget();

   // When any click happens, we want handler to do it's thing.
   w.onClick = &handler;

   // Now suppose, for whatever reason, we want to emulate a click from 
within
   //   our program.
   w.onClick(); // This does not result in handler() being called!
}

The program DOES NOT print "Click happened!" as expected.  Instead, 
w.onClick expanded to something like this:
void delegate() temp = w.onClick();
That's all it does.  It does not call 'temp'.  It just leaves it there.
That's because there is yet another ambiguity:

Does
foo.bar();
become
T delegate() temp = foo.bar();
or
T delegate() temp = foo.bar();
temp();

Try replacing w.onClick(); with w.onClick()(); and it should work.
IMO, w.onClick()(); is not an obvious way at all to call that delegate.

-------------------------------------(5)

Fifth, delegates aren't over with yet.  This isn't nearly as bad as the 
other 4, but it should hit eventually.
Properties can be converted into delegates.
This is shown in the following example code:

struct Point
{
   private int m_x = 0, m_y = 0;

   int x() { return m_x; }
   void x( int value ) { m_x = value; }

   int y() { return m_y; }
   void y( int value ) { m_y = value; }
}

void main()
{
   Point p;

   int delegate() readX = &p.x;
   p.x = 2;
   printf( "%d", readX() ); // prints 2
}

The problem is, what if, for whatever reason, the author of similar code 
someday wanted to demote those properties back to fields?
In that case, the user code that relies on the delegate will fail.
If properties were treated EXACTLY like fields, you wouldn't be able to 
take their address and call it like a function.  You could take their 
address, but it would just be an address to a value.

Realizing that this behavior of properties can be beneficial, it makes 
sense to show that similar code can be written without properties and 
without much added difficulty.  If we wanted to make a readX function 
like in the above code without properties, we could write something like 
this:

struct Point
{
   int x,y;
}

void main()
{
   Point p;
   int getX() { return p.x; }

   int delegate() readX = &getX;
   p.x = 2;
   printf( "%d", readX() ); // prints 2
}

========================================
           How to fix it
========================================

The most direct solution seems to be explicit properties.  This does not 
mean that the current implicit properties need to be removed.  The two 
can exist together, and the disagreement that may follow is whether the 
implicit ones should be deprecated or not.  I'm not going to worry about 
whether to deprecate implicit properties or not right now.

Here is a table that compares some possible semantics of explicit 
properties against the current implicit properties:

<TT>
+----------------+----------+----------+
|   properties   | explicit | implicit |
+----------------+----------+----------+
| write as field |   yes    |   yes    |
+----------------+----------+----------+
| read as field  |   yes    |   yes    |
+----------------+----------+----------+
|     lvalue*    |   yes    |    no    |
+----------------+----------+----------+
|  overridable** |   yes    |   yes    |
+----------------+----------+----------+
|callable as func|    no    |   yes    |
+----------------+----------+----------+
|use as delegate |    no    |   yes    |
+----------------+----------+----------+
| freestanding***|   yes    |   yes    |
+----------------+----------+----------+
</TT>

*lvalue:  If foo is an lvalue, than foo = 5; should set foo's value to 
5, instead of evaluating to 5; and doing nothing.  Implicit write 
properties fake this to some extent, but do not hold in the general 
case.  Example (3) above and the foo++; and foo+=10; issues are examples 
where lvalueness is needed.
**overridable:  This refers to whether or not the property can be 
overridden when found inside a class.
***freestanding:  The property can be placed anywhere in a file, not 
just inside of classes.  They can also be static and have attributes 
like a function would have.

Another possible change might be to forbid return types for explicit 
write properties (they must return void).  Then, there should be 
well-defined behavior in the D spec for code like this:
foo = bar.x = someValue; // x is an explicit property in bar
This could become
bar.x = someValue;
foo = someValue;
or it could be
bar.x = someValue;
foo = bar.x;
or perhaps something else.
The first case is probably the easiest to understand for people reading 
code.  In the second case foo's final value depends entirely on what 
bar.x does.


Now the only issue is, what should the syntax be for the aforementioned 
explicit properties.
Perhaps something like this:

int foo|() { return bar; }
T foo|( int baz ) { bar = baz; }

Which reuses the '|' character.  Thus these functions named foo are 
explicit properties.  Other characters like '.' or '^' could be used as 
well.  This type of syntax disambiguated templates, it should be able to 
do the same for properties.

Another option would be to reuse a keyword, perhaps inout.

inout
{
   int foo() { return bar; }
   T foo( int baz ) { bar = baz; }
}

which would be equivalent to

inout int foo() { return bar; }
inout T foo( int baz ) { bar = baz; }

Thus 'inout' would be an attribute that enforces explicit propertyness 
on a function or method.
I suppose 'in' could be used for write properties and 'out' for read 
props, but then the curly braced attribute-has-scope syntax would not 
work so well.
At the cost of a keyword, some intuitiveness could be gained.  If said 
single keyword can be added, then perhaps add the keyword 'property' 
with syntax similar to the inout example.

This post is a bit lengthy.  So assuming you've read even part of it, 
thank you for your time.
- Chad
Aug 17 2007
next sibling parent reply Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Chad J wrote:
 Properties are a blemish of the D language.  
[snip]
     printf( "Click happened!" );
     // please forgive the use of printf, I don't know whether you are using
     //   Tango or Phobos.
(Seems D is regressing. There used to be a standard library.)
 ========================================
           How to fix it
 ========================================
 
 The most direct solution seems to be explicit properties.
[snip]
 This post is a bit lengthy.  So assuming you've read even part of it, 
 thank you for your time.
I agree with all issues listed with properties. Properties can't be callable both with and without () in order support delegate properties transparently, which means there has to be way to mark them as non-functions. But I am not fully sure I understand your solution to these problems: a.prop++; What should this evaluate to assuming a.prop is an explicit property? What if a.prop returns a reference type (class) with an opPostInc? Likewise: a.prop.y = 7; What should this evaluate to? Any difference if a.prop evaluates to a reference type or a value type? -- Oskar
Aug 17 2007
parent Sean Kelly <sean f4.ca> writes:
Oskar Linde wrote:
 Chad J wrote:
 
 But I am not fully sure I understand your solution to these problems:
 
 a.prop++;
 
 What should this evaluate to assuming a.prop is an explicit property?
Ideally, there should be a way to have both value and reference-based 'get' properties. Sean
Aug 17 2007
prev sibling next sibling parent reply Sean Kelly <sean f4.ca> writes:
Chad J wrote:
 
 This post is a bit lengthy.  So assuming you've read even part of it, 
 thank you for your time.
All good points. I'd like properties in D to address them all somehow. The 'ref' keyword in 2.0 solves some of the issues, but not others. If doing so requires a special syntax then I'm all for it. Sean
Aug 17 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Chad J wrote:
 This post is a bit lengthy.  So assuming you've read even part of it, 
 thank you for your time.
All good points. I'd like properties in D to address them all somehow. The 'ref' keyword in 2.0 solves some of the issues, but not others. If doing so requires a special syntax then I'm all for it. Sean
What issues does ref fix? You can't return a ref even in 2.0, AFAIK. --bb
Aug 17 2007
parent reply Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 Sean Kelly wrote:
 Chad J wrote:
 This post is a bit lengthy.  So assuming you've read even part of it, 
 thank you for your time.
All good points. I'd like properties in D to address them all somehow. The 'ref' keyword in 2.0 solves some of the issues, but not others. If doing so requires a special syntax then I'm all for it.
What issues does ref fix? You can't return a ref even in 2.0, AFAIK.
Oh. I assume this is a planned feature then? Otherwise I see little reason to change "inout" to "ref." Sean
Aug 17 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Bill Baxter wrote:
 Sean Kelly wrote:
 Chad J wrote:
 This post is a bit lengthy.  So assuming you've read even part of 
 it, thank you for your time.
All good points. I'd like properties in D to address them all somehow. The 'ref' keyword in 2.0 solves some of the issues, but not others. If doing so requires a special syntax then I'm all for it.
What issues does ref fix? You can't return a ref even in 2.0, AFAIK.
Oh. I assume this is a planned feature then?
I hope so. That would be great.
 Otherwise I see little 
 reason to change "inout" to "ref."
I thought the reason was more to do with const. Given a const object passed by reference, calling it 'inout' doesn't make much sense. --bb
Aug 17 2007
parent reply Sean Kelly <sean f4.ca> writes:
Bill Baxter wrote:
 Sean Kelly wrote:
 
 Otherwise I see little reason to change "inout" to "ref."
I thought the reason was more to do with const. Given a const object passed by reference, calling it 'inout' doesn't make much sense.
This is the explanation I remember as well. But I'm not sure it justifies the addition of a general-purpose sounding keyword simply for parameter passing. "inout" may have been a bit confusing in light of const reference passing, but is consistency with "in" and "out" made it arguably worth retaining. Sean
Aug 17 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Sean Kelly wrote:
 Bill Baxter wrote:
 Sean Kelly wrote:

 Otherwise I see little reason to change "inout" to "ref."
I thought the reason was more to do with const. Given a const object passed by reference, calling it 'inout' doesn't make much sense.
This is the explanation I remember as well. But I'm not sure it justifies the addition of a general-purpose sounding keyword simply for parameter passing. "inout" may have been a bit confusing in light of const reference passing, but is consistency with "in" and "out" made it arguably worth retaining.
It's all the more perplexing given that the current D 2.x compiler segfaults on functions declared with const ref parameters. If const refs were the reason for changing 'inout' to 'ref' it is odd that they don't work straight out of the gate. --bb
Aug 17 2007
prev sibling next sibling parent reply Daniel Keep <daniel.keep.lists gmail.com> writes:
[snip]

I emphatically agree that *something* needs to be done to address the
shortcomings of properties in D.  I mean, what's the point of having
increment/decrement and the op= operators if you cannot use them in a
large number of places?

I do have to say that I'm not a big fan of the symbol-based syntax.
Also, the inout system seems a bit... off.  The problem I have with it
is that *both* the getter and setter are marked inout, which doesn't
make a lot of sense.

Another possibility is to use contract-style notation:

int foo
{
    in(int baz) { bar = baz; }
    out() { return bar; }
}

Or even just dropping "inout" and using "in" and "out" explicitly:

in void foo(int baz) { bar = baz; }
out int foo() { return bar; }

On another note, I think you should expand on exactly how different
kinds of statements should be rewritten.  For instance, you haven't
clarified what happens here:

w.rect.x = 10;

	-- Daniel
Aug 17 2007
parent Chad J <gamerChad _spamIsBad_gmail.com> writes:
Daniel Keep wrote:
 [snip]
 
 I emphatically agree that *something* needs to be done to address the
 shortcomings of properties in D.  I mean, what's the point of having
 increment/decrement and the op= operators if you cannot use them in a
 large number of places?
 
 I do have to say that I'm not a big fan of the symbol-based syntax.
 Also, the inout system seems a bit... off.  The problem I have with it
 is that *both* the getter and setter are marked inout, which doesn't
 make a lot of sense.
 
 Another possibility is to use contract-style notation:
 
 int foo
 {
     in(int baz) { bar = baz; }
     out() { return bar; }
 }
 
 Or even just dropping "inout" and using "in" and "out" explicitly:
 
 in void foo(int baz) { bar = baz; }
 out int foo() { return bar; }
 
Looks like it will work, and is fine by me.
 On another note, I think you should expand on exactly how different
 kinds of statements should be rewritten.  For instance, you haven't
 clarified what happens here:
 
 w.rect.x = 10;
 
 	-- Daniel
Aug 17 2007
prev sibling next sibling parent reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
Alright I forgot to mention what something like "a.prop.x = foo;" would 
expand to.

It would work like this:

a.prop.x = foo;

expands to

auto temp = a.prop;
temp.x = foo;
a.prop = temp;

Whether temp is a reference or value type need not matter, it should 
work exactly as it would if there was a variable there instead of a 
property.
I suppose Sean's suggestion of allowing programmers to trap reference 
and value uses of properties would work, but I am skeptical that it 
might break the close compatibility between plain variables (fields) and 
properties.  It should be feasible as long as there is a way to make a 
property equivalent to a field wrt calling syntax/semantics.

Longer chains should be broken down as such:

a.prop1.prop2.field.prop3.x = foo;

->

auto temp1 = a.prop1;
auto temp2 = temp1.prop2;
auto temp3 = temp2.field.prop3;
temp3.x = foo;
temp2.field.prop3 = temp3;
temp1.prop2 = temp2;
a.prop1 = temp1;

Hope that helps.
Aug 17 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Chad J wrote:
 Alright I forgot to mention what something like "a.prop.x = foo;" would 
 expand to.
 
 It would work like this:
 
 a.prop.x = foo;
 
 expands to
 
 auto temp = a.prop;
 temp.x = foo;
 a.prop = temp;
 
 Whether temp is a reference or value type need not matter, it should 
 work exactly as it would if there was a variable there instead of a 
 property.
 I suppose Sean's suggestion of allowing programmers to trap reference 
 and value uses of properties would work, but I am skeptical that it 
 might break the close compatibility between plain variables (fields) and 
 properties.  It should be feasible as long as there is a way to make a 
 property equivalent to a field wrt calling syntax/semantics.
 
 Longer chains should be broken down as such:
 
 a.prop1.prop2.field.prop3.x = foo;
 
 ->
 
 auto temp1 = a.prop1;
 auto temp2 = temp1.prop2;
 auto temp3 = temp2.field.prop3;
 temp3.x = foo;
 temp2.field.prop3 = temp3;
 temp1.prop2 = temp2;
 a.prop1 = temp1;
 
 Hope that helps.
That makes some sense. Although just avoiding the copying by allowing either references or a way to create a light proxy that overloads opDot would be more useful. Then you could return a proxy from 'a' that contains just a pointer to prop2. Either way, D sorely needs a way to overload opDot (akin to C++'s operator->). Not having it eliminates a huge class of useful ways to create value wrappers. There's some seriously cool potential for a CTFE template opDot that takes a string giving the member name. --bb
Aug 17 2007
prev sibling next sibling parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Chad J wrote:
 Properties are a blemish of the D language.  In the first part of this
 post, I'll explain why and try to show how these properties are /not/
 good or even better than properties in other languages.  Then I'll
 suggest some possible solutions.  I don't care if the solutions I
 suggest are used, but I will be very thankful if this gets fixed somehow.
 
Great that you took the time to go through it properly. I agree that all the issues you mentioned need to be fixed somehow. big no-no I find this worth considering. Who uses "property" as an identifier name anyway? -- Remove ".doesnotlike.spam" from the mail address.
Aug 17 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Deewiant wrote:
 Chad J wrote:
 Properties are a blemish of the D language.  In the first part of this
 post, I'll explain why and try to show how these properties are /not/
 good or even better than properties in other languages.  Then I'll
 suggest some possible solutions.  I don't care if the solutions I
 suggest are used, but I will be very thankful if this gets fixed somehow.
Great that you took the time to go through it properly. I agree that all the issues you mentioned need to be fixed somehow.
Ditto.

 big no-no I find this worth considering. Who uses "property" as an identifier
 name anyway?
Well, 'property' may be in use by people writing property-editor widgets like in IDEs. But still, I'd go for a new 'property' keyword. Will probably clash with far fewer existing uses that 'ref' did. --bb
Aug 17 2007
prev sibling next sibling parent reply BCS <ao pathlink.com> writes:
Reply to Chad,

 -------------------------------------(1)
 
 First, the very common ambiguity:
 
 Does
 foo.bar++;
 become
 foo.bar( foo.bar + 1 );
 or
 foo.bar() + 1;
 ??
 This is why we can't write things like this:
 
 ubyte[] array = new ubyte[50];
 //...
 array.length++;
 //...
 array.length += 10;
 // Note how builtin types use properties heavily.
 Which means a large set of our beloved shortcut operators are
 completely broken on properties.
 
This is not an syntax issue with how proposes are defined, but the semantics of there use. It can be fixed with minor changes.
 -------------------------------------(2)
[...]
 -------------------------------------(3)
 
 Third, there is a problem with value types and properties. Consider
 the following code:
 
short version: struct S {int i; void I(int j){i=j;} } struct P {S s void GetS() { return s;} } P p; p.GetS.I = 5; // assignes 5 to temp, not member of p This is a reference semantics issue. The same isses happens to general functions.
 -------------------------------------(4)
 
 Fourth, delegates get the shaft.
Short version: void delegate() getDG(); getDG(); // calls getDG giving a delgate getDG(); // calls getDG, does not call returned delegate good point.
 
 -------------------------------------(5)
 
 Fifth, delegates aren't over with yet.  This isn't nearly as bad as
 the
 other 4, but it should hit eventually.
 Properties can be converted into delegates.
[...]
 The problem is, what if, for whatever reason, the author of similar
 code
 someday wanted to demote those properties back to fields?
 In that case, the user code that relies on the delegate will fail.
[...]
 Realizing that this behavior of properties can be beneficial, it makes
 sense to show that similar code can be written without properties and
 without much added difficulty.
[...]
 struct Point
 {
 int x,y;
 }
 void main()
 {
 Point p;
 int getX() { return p.x; }
 int delegate() readX = &getX;
 p.x = 2;
 printf( "%d", readX() ); // prints 2
 }
ouch! that sounds worse than the problem you point out.
 ========================================
 How to fix it
 ========================================
[...]
 *lvalue:  If foo is an lvalue, than foo = 5; should set foo's value to
 5, instead of evaluating to 5; and doing nothing.  Implicit write
 properties fake this to some extent, but do not hold in the general
 case.  Example (3) above and the foo++; and foo+=10; issues are
 examples
 where lvalueness is needed.
I would disagree. I would clam that the issue is that implicit properties can be used a lvalues or rvalues, but not both. [......] The basis of your thoughts seem to come from the idea that hiding the fact that properties are functions would be a good thing. I would assert that this is highly debatable. I will concede that the current semantics of properties being not quite interchangeable with fields is sub optimal. However fixing this by creating a new construct that, in the end, is identical to a function but with a number of things forbidden seems somehow wrong. As I see it the issues you bring up amount to: 1> no L/R-value usage (++, +=, etc, use as an inout or out arg) 2> they can lead to hard to find reference semantics issues 3> syntax problems with properties retuning delegates 4> not interchangeable with fields solution. Also, the proposed solution doesn't fix the issue because taking the address of a field is allowed, even with explicit properties, this wouldn't work IMHO there are a few minor issues with proposes that could uses a little work. However I don't see much to be gained by you proposal for explicit properties.
 This post is a bit lengthy.  So assuming you've read even part of it,
 thank you for your time.
 - Chad
It's good to see people thinking about this stuff. I may not see your ideas to be the "right" way but, then again, I want different things out of D than many people do.
Aug 17 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
BCS wrote:
 Reply to Chad,
 The basis of your thoughts seem to come from the idea that hiding the 
 fact that properties are functions would be a good thing. I would assert 
 that this is highly debatable.
Ah yes. Although I generally agree with Chad, you did remind me of something. In my Luigi GUI library I use properties for lots of widget settings (things like a button's label, etc), and also rely on being able to call those as delegates for signal-slot connections to set the properties. That's handy, but it only works for members/properties that are written as functions. What I should probably do is to auto-generate a delegate wrapper for pure data members if you try to use one as a slot for a delegate(T setval) type of signal. The other issue it runs into is the overload ambiguity of void foo(int) {} int foo() {} when you take the function reference. One way to resolve the desire to use properties, not pretend they're functions, and still allow usage as a function when possible, would be to allow giving the functions names different from the property name. So then you could do something like: void set_foo(int) {...} int get_foo() {...} property foo(get_foo,set_foo); Or property foo { void set_foo(int) {...} int get_foo() {...} } Of course now you can already make aliases if you just want unambiguous function names to take the address of. (Doesn't prevent using foo() as a function though) void set_foo(int) {...} int get_foo() {...} alias set_foo foo; alias get_foo foo; // not 100% sure this works... --bb
Aug 17 2007
prev sibling parent reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
BCS wrote:
 Reply to Chad,
 
 -------------------------------------(1)

 First, the very common ambiguity:

 Does
 foo.bar++;
 become
 foo.bar( foo.bar + 1 );
 or
 foo.bar() + 1;
 ??
 This is why we can't write things like this:

 ubyte[] array = new ubyte[50];
 //...
 array.length++;
 //...
 array.length += 10;
 // Note how builtin types use properties heavily.
 Which means a large set of our beloved shortcut operators are
 completely broken on properties.
This is not an syntax issue with how proposes are defined, but the semantics of there use. It can be fixed with minor changes.
What minor changes? If you're going to suggest giving implicit properties lvalueness, then this would break any code that relies on the quirks of current implicit properties. I was hoping to preserve reverse compatibility with my solution, especially since Walter seems conservative about doing anything that breaks existing code.
 
 -------------------------------------(2)
[...]
Promotion != lvalueness extensibility issues.
 -------------------------------------(3)

 Third, there is a problem with value types and properties. Consider
 the following code:
short version: struct S {int i; void I(int j){i=j;} } struct P {S s void GetS() { return s;} } P p; p.GetS.I = 5; // assignes 5 to temp, not member of p This is a reference semantics issue. The same isses happens to general functions.
Exactly. I don't want function semantics with my properties. I already have function semantics with my functions.
 -------------------------------------(4)

 Fourth, delegates get the shaft.
Short version: void delegate() getDG(); getDG(); // calls getDG giving a delgate getDG(); // calls getDG, does not call returned delegate good point.
 -------------------------------------(5)

 Fifth, delegates aren't over with yet.  This isn't nearly as bad as
 the
 other 4, but it should hit eventually.
 Properties can be converted into delegates.
[...]
 The problem is, what if, for whatever reason, the author of similar
 code
 someday wanted to demote those properties back to fields?
 In that case, the user code that relies on the delegate will fail.
[...]
 Realizing that this behavior of properties can be beneficial, it makes
 sense to show that similar code can be written without properties and
 without much added difficulty.
[...]
 struct Point
 {
 int x,y;
 }
 void main()
 {
 Point p;
 int getX() { return p.x; }
 int delegate() readX = &getX;
 p.x = 2;
 printf( "%d", readX() ); // prints 2
 }
ouch! that sounds worse than the problem you point out.
I disagree, but in light of what you value, it becomes an aesthetics vs functionality issue.
 ========================================
 How to fix it
 ========================================
[...]
 *lvalue:  If foo is an lvalue, than foo = 5; should set foo's value to
 5, instead of evaluating to 5; and doing nothing.  Implicit write
 properties fake this to some extent, but do not hold in the general
 case.  Example (3) above and the foo++; and foo+=10; issues are
 examples
 where lvalueness is needed.
I would disagree. I would clam that the issue is that implicit properties can be used a lvalues or rvalues, but not both.
Still doesn't handle promotion/demotion between fields and properties. Also, if you have a way to make the lvalue&rvalue semantics work, let me hear it.
 
 
 [......]
 
 The basis of your thoughts seem to come from the idea that hiding the 
 fact that properties are functions would be a good thing. I would assert 
 that this is highly debatable. I will concede that the current semantics 
 of properties being not quite interchangeable with fields is sub 
 optimal. However fixing this by creating a new construct that, in the 
 end, is identical to a function but with a number of things forbidden 
 seems somehow wrong.
 
Actually, I am suggesting that properties not be functions at all. They will of course share the notion of a body of executable code, but for all intents and purposes they are a way of accessing data while introducing side effects.
 As I see it the issues you bring up amount to:
 
 1> no L/R-value usage (++, +=, etc, use as an inout or out arg)
 2> they can lead to hard to find reference semantics issues
 3> syntax problems with properties retuning delegates
 4> not interchangeable with fields
 

how?

I have suffered many hours of debugging thanks to this "feature". Don't tell me it isn't a problem!

I disagree, but regardless let's at least fix it while we are dealing with the related stuff.

 proposed solution. Also, the proposed solution doesn't fix the issue 
 because taking the address of a field is allowed, even with explicit 
 properties, this wouldn't work
 
If the proposed solution is not good enough, please give a better one. Suggestions are welcome and encouraged. As for the address of a field... perhaps if taking the address of a field returned a delegate... nah. This is somewhat of a corner case, but you've got me there, for now. I'd love to see someone come up with a slick solution to this. considered "unsafe" and done very infrequently. It makes me want to address of a property, and I probably will at some point. Even if the addressing thing can't be fixed, I'd at least like to see the rest of the stuff get fixed.
 
 IMHO there are a few minor issues with proposes that could uses a little 
 work. However I don't see much to be gained by you proposal for explicit 
 properties.
 
 This post is a bit lengthy.  So assuming you've read even part of it,
 thank you for your time.
 - Chad
It's good to see people thinking about this stuff. I may not see your ideas to be the "right" way but, then again, I want different things out of D than many people do.
Aug 18 2007
parent reply BCS <ao pathlink.com> writes:
Reply to Chad,

 BCS wrote:
 
 Reply to Chad,
 
 -------------------------------------(1)
 
 First, the very common ambiguity:
 
 Does
 foo.bar++;
 become
 foo.bar( foo.bar + 1 );
 or
 foo.bar() + 1;
 ??
[...]
 This is not a syntax  issue with how properties are defined, but the
 semantics of there use.
 It can be fixed with minor changes.
What minor changes? If you're going to suggest giving implicit properties lvalueness, then this would break any code that relies on the quirks of current implicit properties. I was hoping to preserve reverse compatibility with my solution, especially since Walter seems conservative about doing anything that breaks existing code.
How? Letting implicit properties be simultaneously L and R values (they can now be either, just not both at the same time) won't break anything that I know of. Anything that is legal now, still would be, and any cases where the behavior would be different, were illegal to begin with.
 -------------------------------------(2)
 
[...]
Promotion != lvalueness extensibility issues.
If you see them as disticntly different points, then we must be looking at things at different levels. I see both 1 and 2 as saying: "properties can
 -------------------------------------(3)
 
 Third, there is a problem with value types and properties. Consider
 the following code:
 
short version: struct S {int i; void I(int j){i=j;} } struct P {S s void GetS() { return s;} } P p; p.GetS.I = 5; // assignes 5 to temp, not member of p This is a reference semantics issue. The same isses happens to general functions.
Exactly. I don't want function semantics with my properties. I already have function semantics with my functions.
I see your point and I strongly disagree. Under the covers, it IS a function and I /don't/ want that hidden.
 -------------------------------------(5)
 
 Fifth, delegates aren't over with yet.
[...]
 the user code that relies on the delegate will fail.
[...] ouch! that sounds worse than the problem you point out.
I disagree, but in light of what you value, it becomes an aesthetics vs functionality issue.
Unless you can trust the compiler to inline delegate literals (and the potentially virtual function call underneath), it is a performance issue.
 ========================================
 How to fix it
 ========================================
[...]
 *lvalue:  If foo is an lvalue, than foo = 5; should set foo's value
 to
 5, instead of evaluating to 5; and doing nothing.  Implicit write
 properties fake this to some extent, but do not hold in the general
 case.  Example (3) above and the foo++; and foo+=10; issues are
 examples
 where lvalueness is needed.
I would disagree. I would clam that the issue is that implicit properties can be used a lvalues or rvalues, but not both.
Still doesn't handle promotion/demotion between fields and properties.
My point was not intended to. It is a minor issue claming that the mechanics of one of your (valid) issues with implicit properties is incorrectly stated.
 Also, if you have a way to make the lvalue&rvalue semantics work, let
 me hear it.
 
I think that you actually described somewhere the solution I wold use. Use the R-value property (getter) to get the value, do the operation, then use the L-value property (setter) to push it back in. struct S { ... int i(); void i(int); } S s; s.i++; // becomes { auto __tmp = s.i; __tmp++; s.i = __tmp; } If only one or the other property exits then it is assumed the author wants to disallow this type of operation.
 [......]
 
 The basis of your thoughts seem to come from the idea that hiding the
 fact that properties are functions would be a good thing. I would
 assert that this is highly debatable. I will concede that the current
 semantics of properties being not quite interchangeable with fields
 is sub optimal. However fixing this by creating a new construct that,
 in the end, is identical to a function but with a number of things
 forbidden seems somehow wrong.
 
Actually, I am suggesting that properties not be functions at all. They will of course share the notion of a body of executable code, but for all intents and purposes they are a way of accessing data while introducing side effects.
Will they be accessed by doing a call/return sequence at the machine code level? If so, I would assert they are functions for all purposes I'm interested in. If it would be implemented by some other means, then I have have missed some major point of your post. D is a low level language in many respects, as such, if it looks like a function when it comes out of the compiler, I want to be able to treat it as a function when it goes in.
 As I see it the issues you bring up amount to:
 
 1> no L/R-value usage (++, +=, etc, use as an inout or out arg)
 2> they can lead to hard to find reference semantics issues
 3> syntax problems with properties retuning delegates
 4> not interchangeable with fields

 
how?
same as above
 

 
I have suffered many hours of debugging thanks to this "feature". Don't tell me it isn't a problem!
It's not a problem with /properties/. dropping properties compleaty wouldn't remove this type of isses. It might make some cases easyer to spot but nothing more. As to converting from a member to a property changing things, that

 proposed solution. Also, the proposed solution doesn't fix the issue
 because taking the address of a field is allowed, even with explicit
 properties, this wouldn't work
 
If the proposed solution is not good enough, please give a better one. Suggestions are welcome and encouraged.
Nothing yet. IMHO, your proposed solution, while maybe appropriate for higher level languages, is not right for D. If I see a solution that solves these issues without cause to many of its own problems, I would be interested.
 As for the address of a field... perhaps if taking the address of a
 field returned a delegate... nah.
 This is somewhat of a corner case, but you've got me there, for now.
 I'd love to see someone come up with a slick solution to this.


 was
 considered "unsafe" and done very infrequently.  It makes me want to

 address of a property, and I probably will at some point.
 Even if the addressing thing can't be fixed, I'd at least like to see
 the rest of the stuff get fixed.
I'll bet you can't. You can't use one for an out or ref argument (I just cheked) but you can use a member.
Aug 18 2007
parent reply Jason House <jason.james.house gmail.com> writes:
BCS wrote:
 I see your point and I strongly disagree. Under the covers, it IS a 
 function and I /don't/ want that hidden.
By the very virtue that D makes the syntax identical, it _is_ trying to hide that fact. Either properties must try to act like members or the usage of properties should not mirror members.
Aug 18 2007
parent BCS <ao pathlink.com> writes:
Reply to Jason,

 BCS wrote:
 
 I see your point and I strongly disagree. Under the covers, it IS a
 function and I /don't/ want that hidden.
 
By the very virtue that D makes the syntax identical, it _is_ trying to hide that fact. Either properties must try to act like members or the usage of properties should not mirror members.
Good point. To some extent it does. However, that just doesn't feel the same as Chad's suggestion. I'm not sure exactly why, but I thing it has something to do with the fact that currently D only hides the function'ness syntactically, but the proposal does so semantically.
Aug 18 2007
prev sibling next sibling parent reply Ender KaShae <astrothayne gmail.com> writes:
Chad J Wrote:


 If the proposed solution is not good enough, please give a better one. 
 Suggestions are welcome and encouraged.
 
I suggest something similar to python properties, for those who are not familior with it a property is created with property(getter, setter) so with my idea property (or inout or some other keyword) would be a new type, sort of like a function or delegate, but with behavior like that described by Chad. The syntax would be somthing like: int getX(){ return x;} void setX(int i){x = i;} property int X(&getX, &setX); the problem is that unlike in python we cannot use keywords to set paramaters so some syntax would have to be made for write only properties, maybe if we used inout instead of property: //read only in int x(&getX); //write only out int x(&setX); //read and write inout int x(&getX, &setX) any read would call the reading function (.reader of the property object) any write would call the writing function (.writer of the property object) so obj.x++ would be the same as: obj.x.writer(obj.x.reader() + 1) when read the property will actually return a special wrapper that sends itself to the writer whenever it is assigned to the address of the property cannot be used as a function or delegate, but can be used as the address of the type
Aug 18 2007
next sibling parent reply BCS <ao pathlink.com> writes:
Reply to Ender,

 int getX(){ return x;}
 void setX(int i){x = i;}
 property int X(&getX, &setX);
 
 the problem is that unlike in python we cannot use keywords to set
 paramaters so some syntax would have to be made for write only
 properties, maybe if we used inout instead of property:
 
property int X(void, &setX); property int Y(&getY, void); ?? I'm not sure I like this more than what we have, but it does leave open getting at the functions which I like. It might grow on me.
Aug 18 2007
parent reply "Jb" <jb nowhere.com> writes:
"BCS" <ao pathlink.com> wrote in message 
news:ce0a3343d64a8c9afeb721a0ab0 news.digitalmars.com...
 Reply to Ender,

 int getX(){ return x;}
 void setX(int i){x = i;}
 property int X(&getX, &setX);

 the problem is that unlike in python we cannot use keywords to set
 paramaters so some syntax would have to be made for write only
 properties, maybe if we used inout instead of property:
property int X(void, &setX); property int Y(&getY, void); ?? I'm not sure I like this more than what we have, but it does leave open getting at the functions which I like. It might grow on me.
Delphi does this. _foo int; int getFoo() { return _foo; }; vood setFoo(i: integer) {_foo = i; ); any of the following are valid... property int foo : read getFoo write setFoo; property int foo : read _foo write _foo; property int foo : read getFoo write _foo; property int foo : read _foo; ect.. I converted the Delphi syntax to be more D like, and its just offered for reference, not a proposal as such. So the property specifies either or both read / write, of which either or both can be direct acess to the field or a proxy function. The proxy functions can be virtual and so can be overriden in subclasses. So you get read only, write only, read/write, direct field acess, static function, or virtual function. And you can still expose the function if you want. jb
Aug 19 2007
parent reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
Jb wrote:
 "BCS" <ao pathlink.com> wrote in message 
 news:ce0a3343d64a8c9afeb721a0ab0 news.digitalmars.com...
 Reply to Ender,

 int getX(){ return x;}
 void setX(int i){x = i;}
 property int X(&getX, &setX);

 the problem is that unlike in python we cannot use keywords to set
 paramaters so some syntax would have to be made for write only
 properties, maybe if we used inout instead of property:
property int X(void, &setX); property int Y(&getY, void); ?? I'm not sure I like this more than what we have, but it does leave open getting at the functions which I like. It might grow on me.
Delphi does this. _foo int; int getFoo() { return _foo; }; vood setFoo(i: integer) {_foo = i; ); any of the following are valid... property int foo : read getFoo write setFoo; property int foo : read _foo write _foo; property int foo : read getFoo write _foo; property int foo : read _foo; ect.. I converted the Delphi syntax to be more D like, and its just offered for reference, not a proposal as such. So the property specifies either or both read / write, of which either or both can be direct acess to the field or a proxy function. The proxy functions can be virtual and so can be overriden in subclasses. So you get read only, write only, read/write, direct field acess, static function, or virtual function. And you can still expose the function if you want. jb
I like where all of this is going. Then the solution becomes one which allows properties to be treated exactly* as members on the using side, yet allows the underlying functions to be used as well. So BCS, would you be willing to settle for such a solution? * I realize we may have to settle for properties being "close enough" to members, due to addressing.
Aug 19 2007
parent reply BCS <ao pathlink.com> writes:
Reply to Chad,

 I like where all of this is going.
 
 Then the solution becomes one which allows properties to be treated
 exactly* as members on the using side, yet allows the underlying
 functions to be used as well.  So BCS, would you be willing to settle
 for such a solution?
Let's say I wouldn't object to it. (I'm still not totally convinced something /needs/ to be done, but I do thing that it's worth looking at)
 
 * I realize we may have to settle for properties being "close enough"
 to members, due to addressing.
You actually could make pointers to properties work just like members by placing the address of the member in invalid memory locations. Then on a seg-v, check if the address is attached to a property and stuff in the right value. I heard that at one point some computer systems used a something like that (but using illegal op codes) to fake having a floating point unit. (BTW, I am NOT suggesting this be used <G>)
Aug 19 2007
parent reply Chad J <gamerChad _spamIsBad_gmail.com> writes:
BCS wrote:

 
 * I realize we may have to settle for properties being "close enough"
 to members, due to addressing.
You actually could make pointers to properties work just like members by placing the address of the member in invalid memory locations. Then on a seg-v, check if the address is attached to a property and stuff in the right value. I heard that at one point some computer systems used a something like that (but using illegal op codes) to fake having a floating point unit. (BTW, I am NOT suggesting this be used <G>)
Pretty clever! Too bad it seems very hardware dependent.
Aug 20 2007
parent Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Chad J wrote:
 BCS wrote:
 
 * I realize we may have to settle for properties being "close enough"
 to members, due to addressing.
You actually could make pointers to properties work just like members by placing the address of the member in invalid memory locations. Then on a seg-v, check if the address is attached to a property and stuff in the right value. I heard that at one point some computer systems used a something like that (but using illegal op codes) to fake having a floating point unit. (BTW, I am NOT suggesting this be used <G>)
Pretty clever! Too bad it seems very hardware dependent.
It would also be hell to implement on x86 and similar architectures. Too many instructions that access memory, all needing to be emulated...
Aug 20 2007
prev sibling parent Christopher Wright <dhasenan gmail.com> writes:
Ender KaShae wrote:
 Chad J Wrote:
 
 
 If the proposed solution is not good enough, please give a better one. 
 Suggestions are welcome and encouraged.
I suggest something similar to python properties, for those who are not familior with it a property is created with property(getter, setter) so with my idea property (or inout or some other keyword) would be a new type, sort of like a function or delegate, but with behavior like that described by Chad. The syntax would be somthing like: int getX(){ return x;} void setX(int i){x = i;} property int X(&getX, &setX);
Or: property int X({ return x; }, (int i) { x = i; });
 the problem is that unlike in python we cannot use keywords to set paramaters
so some syntax would have to be made for write only properties, maybe if we
used inout instead of property:
 
 //read only
 in int x(&getX);
 //write only
 out int x(&setX);
 //read and write
 inout int x(&getX, &setX)
Or: property int ReadonlyX({ return x; }, null); property int WriteonlyX(null, (int i) { x = i; });
 any read would call the reading function (.reader of the property object)
 any write would call the writing function (.writer of the property object)
This is good -- getting the address of the getter and setter functions (&obj.X.reader) will be useful. And getting the address of the property will still allow you to get and set the property. Templates might have trouble with it, though.
 so obj.x++
 
 would be the same as:
 obj.x.writer(obj.x.reader() + 1)
 
 when read the property will actually return a special wrapper that sends
itself to the writer whenever it is assigned to 
 the address of the property cannot be used as a function or delegate, but can
be used as the address of the type 
The address of the property should be an address to a property struct, whose getters and setters would work as usual. Hm... --- class Property(T) { T* value; public void opIncrement() { *value++; } public void opDecrement() { *value--; } public T opAdd(U) (U addend) { return *value + addend; } // ... } class MyClass { private: int _x; public: Property!(int) X; this() { // slightly ugly... X = new Property!(int)(&_x); } } --- If you could overload the dot operator as well, then this and some template magic with __traits would give you everything you want. Without the dot operator, you get everything you need anyway for basic types. And classes are already taken care of. Structs are left out, though.
Aug 19 2007
prev sibling parent Ender KaShae <astrothayne gmail.com> writes:
Christopher Wright Wrote:

 


 when read the property will actually return a special wrapper that sends
itself to the writer whenever it is assigned to 
 the address of the property cannot be used as a function or delegate, but can
be used as the address of the type 
The address of the property should be an address to a property struct, whose getters and setters would work as usual. Hm... --- class Property(T) { T* value; public void opIncrement() { *value++; } public void opDecrement() { *value--; } public T opAdd(U) (U addend) { return *value + addend; } // ... } class MyClass { private: int _x; public: Property!(int) X; this() { // slightly ugly... X = new Property!(int)(&_x); } } --- If you could overload the dot operator as well, then this and some template magic with __traits would give you everything you want. Without the dot operator, you get everything you need anyway for basic types. And classes are already taken care of. Structs are left out, though.
The problem with that approach is it would be difficult to specify any special behavior for the properties (which is an important feature of properties), or am I just missing something. Actually something more like: ---- struct Property(Type){ Type delegate() getter; void delegate(Type) setter; Type opPostIncrement(){ scope temp = getter(); setter(getter() + 1); return temp; } etc... } would work pretty well
Aug 19 2007