Jump to page: 1 2 3
Thread overview
Fixing D's Properties
Aug 17, 2007
Chad J
Aug 17, 2007
Oskar Linde
Aug 17, 2007
Sean Kelly
Aug 17, 2007
Sean Kelly
Aug 17, 2007
Bill Baxter
Aug 17, 2007
Sean Kelly
Aug 17, 2007
Bill Baxter
Aug 17, 2007
Sean Kelly
Aug 17, 2007
Bill Baxter
Aug 17, 2007
Daniel Keep
Aug 17, 2007
Chad J
Aug 17, 2007
Chad J
Aug 17, 2007
Bill Baxter
Aug 17, 2007
Deewiant
Aug 17, 2007
Bill Baxter
Aug 17, 2007
BCS
Aug 17, 2007
Bill Baxter
Aug 18, 2007
Chad J
Aug 18, 2007
BCS
Aug 18, 2007
Jason House
Aug 18, 2007
BCS
August 17, 2007
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!
Unlike other languages with properties like C#, D does not allow easy 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
August 17, 2007
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
August 17, 2007
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
August 17, 2007
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
August 17, 2007
[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
August 17, 2007
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.
August 17, 2007
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.

C# uses the "property" keyword IIRC, and even though adding new keywords is a big no-no I find this worth considering. Who uses "property" as an identifier name anyway?

-- 
Remove ".doesnotlike.spam" from the mail address.
August 17, 2007
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
August 17, 2007
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)

[...]

this is the same as #1 (or am i missing somthing)

> -------------------------------------(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

#1 can be fixed without explicit properties
#2 is not at all a problem in my book
#3 This is a tiny corner case
#4 this is a good point, however, as stated above, I don't like the 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


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.


August 17, 2007
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
« First   ‹ Prev
1 2 3