Last update August 25, 2009

Doc Comments /
Property



Properties    

Table of contents of this page
Properties   
Properties and why they are useful:   
What happens without properties   
Nouns and Verbs   
Problems with D's current property implementation   
Redeeming qualities of D's current syntax   
Semantic rewriting of properties   
Explicit Property Syntax   
Properties as a declaration   
C# style   
Keyworded   
Properties as a function attribute   
opGet_XX and opSet_XX   
getXx and setXx   
Walter's syntax   
xX.get and xX.set   
Keyword solutions   
in and out   
property   
@property   
Migration flow for various properties   
PropertyCombo   
Messages   
"classinfo" property   
FAQ   
Member functions treated as fields   
Why doesn't "array.length++" work?   
Links   

Properties and why they are useful:    

  • Properties allow a class, struct, or module to monitor state changes that occur when accessing fields. When it property is invoked it can set dirty-flags, increment/decrement counters, allocate memory, call events, and any number of other useful side-effects. These side effects can be very important for making sure the internal representation of an object stays in sync with what the external API describes.
// Example of dirty-flags usage.
class Widget
{
    private int widthData;
    private bool doRedraw;

    property int width() { return widthData; }
    property int width(int val)
    {
        // The width changed, so redraw this widget on the next frame.
        doRedraw = true;
        
        return widthData = val;
    }

    // etc
}

// Example of allocating memory with properties.
void main()
{
    int[] array = new int[50];
    array.length++; // Allocate one more.
}
  • Properties allow the use of calculated values in a seamless manner. It is possible with properties to create a member in a struct or class and have it act like a field, except that it requires no storage. More importantly, some values are derived purely from other values, and keeping the derived value in sync can be difficult if it is stored separately. Properties allow the derived value to be calculated lazily whenever it is needed by external code. One example is given below in "What happens without properties". Another is given here:
import std.stdio;
import std.math;

struct Line
{
    float x1, y1;
    float x2, y2;

    float distance()
    {
        auto dx = x2 - x1;
        auto dy = y2 - y1;
        return sqrt(dx*dx + dy*dy);
    }
}

// USAGE:
void main()
{
    Line line;
    line.x1 = 3;
    line.y1 = 0;
    line.x2 = 0;
    line.y2 = 4;
    writefln("%f",line.distance); // prints 5.000000
    line.y2 = 8;
    writefln("%f",line.distance); // prints 8.544003
    line.x1 = 6;
    writefln("%f",line.distance); // prints 10.000000
}
  • Properties are mostly interoperable with fields. In most cases they can be converted to fields, and fields can be converted to properties, all without changing how they are used externally.
    • This is important because it allows APIs to migrate between fields and properties depending on how much expressiveness is needed.
    • There is one notable exception to the interoperability: addressing. Fields have addresses in memory, while properties may not have such a thing.
    • This point is discussed in more detail here.
  • API writers can use properties to enforce a nouns vs verbs distinction.

What happens without properties    

Suppose some D programmer has just written an API:

struct Color
{
    int red, green, blue;
}

They then realize that their API is not as powerful as it could be, so they want to improve it to look something like this:

enum ColorRepresentation
{
    rgb,
    hsb,
    lab,
    // etc
}

struct Color
{
    private int channel1, channel2, channel3;
    private ColorRepresentation representation;

    ColorRepresentation getRepresentation() { ... }
    ColorRepresentation setRepresentation(...) { ... }

    int getRed  () { return calculateRed(); }
    int getGreen() { return calculateGreen(); }
    int getBlue () { return calculateBlue(); }
    // ... corresponding writing functions and such.

    int getHue()        { return calculateHue(); }
    int getSaturation() { return calculateSaturation(); }
    int getBrightness() { return calculateBrightness(); }

    int setHue( int val )
    {
        fromHSB(val,getSaturation(),getBrightness());
        return getHue();
    }
    
    int setSaturation( int val )
    {
        fromHSB(getHue(),val,getBrightness());
        return getSaturation();
    }

    int setBrightness( int val )
    {
        fromHSB(getHue(),getSaturation(),val);
        return getBrightness();
    }
    // etc...
}

There are two possibilities for this API's migration:

    • The API is broken as the (r,g,b) fields no longer exist.
    • The API was written as a function-only API in the first place:
struct Color
{
    private int red, green, blue;

    int getRed  () { return red; }
    int getGreen() { return green; }
    int getBlue () { return blue; }
    int setRed(int val)   { return red = val; }
    int setGreen(int val) { return green = val; }
    int setBlue(int val)  { return blue = val; }
}
Such an API could be migrated to the later state without breaking anything. It is possible to write APIs in this fashion without being a psychic and still be assured that future versions of the API are much less likely to break earlier usages. However, when compared against the API with public data fields allowed, this one is considerably more verbose and violates DRY repeatedly.

With properties, a third path for migration presents itself:

// The property syntax given in this example is an arbitrary choice.
// This code is currently invalid D code due to the introduction of
//   property notation.

enum ColorRepresentation
{
    rgb,
    hsb,
    lab,
    // etc
}

struct Color
{
    private int channel1, channel2, channel3;
    private ColorRepresentation representation;

    ColorRepresentation getRepresentation() { ... }
    ColorRepresentation setRepresentation(...) { ... }

    property int red  () { return calculateRed(); }
    property int green() { return calculateGreen(); }
    property int blue () { return calculateBlue(); }
    // ... corresponding writing functions and such.

    property int hue()        { return calculateHue(); }
    property int saturation() { return calculateSaturation(); }
    property int brightness() { return calculateBrightness(); }

    property int hue( int val )
        { fromHSB(val,saturation,brightness); return hue; }

    property int saturation( int val )
        { fromHSB(hue,val,brightness); return saturation; }

    property int brightness( int val )
        { fromHSB(hue,saturation,val); return brightness; }

    // etc...
}

// USAGE:
Color c;
c.red = 0x7F;
c.green = 0x7F;
c.blue = 0x7F;
// ...
c.hue = 280;
// and so on.
Thus some amount of future proofing can be achieved without being a psychic or using a verbose and needlessly redundant function-only API.

Nouns and Verbs    

Some programmers feel that APIs should expose fields and functions as nouns and verbs respectively. Properties are generally considered noun-like in their usage. A naming convention that follows this ideal will generally name fields and properties with english nouns, and name functions with english verbs. The parentheses in function calls are used as a queue to the reader's mind that the code element in use is verb-like in nature, while a lack of parentheses tends to imply a noun-like element. Explicit properties (currently absent from D) allow an API writer to prevent users from invoking properties with parentheses (ex: green()). This control over how a property is invoked explicit assists API writers in enforcing the noun vs verb distinction.

This distinction is useful in some cases where a single word in english has different meanings as either a noun or a verb. One example is the word "empty" which, in the context of a data structure like a range in D2, could either be a function that empties the range (verb) or a property which indicates whether the range is empty or not (noun). In this case, seeing array.empty() tells the reader that the array gets turned into a zero-element array, while array.empty looks like an expression that returns a boolean indicating whether the array is empty or not. In D2, the noun-form is used for ranges. Another example likely to come up in programming is "transform" where object.transform() seems like it transforms the object while object.transform seems like it is a child of object and it describes how to do transformations.

Problems with D's current property implementation    

D's current implementation of properties has had a number of complaints leveled against it:

  • (1) Property modification by side effects is forbidden. "array.length++;" gives the error "Error: array.length is not an lvalue". This results from "array.length++;" being rewritten as "array.length()++;", an expression that should rightfully not compile.
  • (2) The modification of property members leads to surprising results. If a is a property, then "a.b = 3;" will do absolutely nothing when executed.
    • This results from "a.b = 3;" being rewritten as "a().b = 3;". Unfortunately, this expression does compile.
    • There are cases where side effects on a member of a property are permissible, such as when b is a function: "b().c(5)". It might become more clear when written as something like "GetProcessOutput().Output(5);". (Suppose GetProcessOutput() returns a struct with references to other objects.)
    • Another case where side effects on a member of a property are useful is in proxy structs:
/* MyNiftyPointerTo!(T) grabs all of the compile-time reflection info from T
and forwards all of its functions and such.  Then it becomes a reference to some
T, but not a reference in a sense the compiler knows about. */
struct MyNiftyPointerTo(T)
{
  ...
}

struct S
{
   MyNiftyPointerTo!(C) getValue() {...}
}

void main()
{
  S s;
  s.getValue.foo(); // oops, compiler says MyNiftyPointerTo!(C) is an rvalue
because it's a struct.
}
    • The presence of the above useful cases for rvalue mutation makes this problem more difficult to solve, since simply forbidding all rvalue mutation will harm D's expressiveness.
    • There is a ticket in D's bug tracker for this.
  • (3) D's properties do not have a distinctive syntax, so IDEs and 3rd party tools cannot identify properties. As an example, SharpDevelop is shown below analysing some C# code with properties. Notice how it identifies different kinds of class members with different icons in the frame on the right.
  • (4) It is impossible for the property writer to specify how a property should be called (with, without (), or either) and have the compiler enforce the convention.
  • (5) The delegate ambiguity. It is not possible to write properties that return delegates or function pointers:
void main()
{
    int foo() { return 42; }
    int delegate() bar() { return &foo; }

    auto baz = bar();
    // Does that expand as
    //   bar()(); // Type is int
    //   or
    //   bar();   // Type is int delegate()
    // ??
}
  • (6) It violates DRY in many cases, needing to duplicate the type and name (or some variant of the name) numerous times:
// Not an unreasonable example of current property definition.
// Can be improved somewhat, but mostly just with
// metaprogramming/mixins, and that's too ugly for such a common
// construct and it doesn't solve the other issues anyway.

private int _width;              // type: 1, name: 1
int width()                      // type: 2, name: 2
{
    return _width;           // type: 2, name: 3
}
int width(int value)             // type: 4, name: 4
{
    return (_width = value); // type: 4, name: 5
}

Problems (1) and (2) can also be seen in some problems with operator overloading:

class Foo
{
    int bar;
    int opIndex( int val ) { return bar; }
    int opIndexAssign( int bar, int val ) { return bar = val; }
}

void main()
{
    Foo foo = new Foo();
    foo[5]++; // Error: foo.opIndex(5) is not an lvalue
}

Notably, C#, a language with explicit properties, simply gives errors in cases (1) and (2) when a struct is returned from a property.

Redeeming qualities of D's current syntax    

  • D's current properties are fairly easy to implement.
  • Omissible parentheses are a part of D's template syntax: i.e. myTemplate!int vs. myTemplate!(int)
  • D's current properties allow a concept that has been refered to as function-property duality. This duality allows function call chaining to become cleaner in some cases:
Stdout("Hello, World!").newline.newline;
// Compare to: Stdout("Hello, World!").newline().newline();

auto data = (cast(string)std.file.read(filename)).chomp.split;
set_colour.uses =
    (new Texture!float4(mod,"TRANSFER", tranfer_data)).normalize.clamp.linear;

'uses' is overloaded with a variadic version:

CCK_engine.uses( iCCK, a_p, a_p2, a_t);

Semantic rewriting of properties    

It is possible to solve problems (1) and (2) without changes to D's syntax. This can be done by rewriting lvalue expressions in the following way:

array.length++;
becomes
auto t = array.length;
t++;
array.length = t;

The resulting code does exactly what most programmers would expect: the array's length is increased by one, allocating more memory as needed.

The idea is to rewrite calls to properties such that if a getter is found within an expression that produces side effects, then the setter is guaranteed to be called. Put more plainly: if there is any chance the property should be altered by the expression, then the setter will definately be called. If there is no chance the property should be altered by the expression, then the setter will not be called. The rewrite only applies to expressions with side effects:

array.length += bar + array.length + foo;
becomes
auto t1 = array.length;

// The right-hand-side has no side effects and thus remains the same.
t1 += bar + array.length + foo;

array.length = t1;

This change would break backwards compatibility. That's because when a getter returns lvalues like objects or ref returns (Note: ref return properties are currently impossible due to a bug), it will currently be accepted by the compiler and the returned value will be modified by the enclosing expression, but the setter will not be called. Such code is reasonable and useful. Example:

import std.stdio;

class Foo
{
    int bar;
}

Foo fooData;
Foo foo() { return fooData; }

void main()
{
    fooData = new Foo();
    fooData.bar = 0;
    foo.bar = 42; // returns a reference type, not a value type.
    writefln("%d",fooData.bar); // prints 42
}
Currently this code compiles and runs as expected. Should the expression rewriting logic be implemented, the above code would fail to compile since there is no setter for the property foo. The solution is to add a trivial setter in these cases:
Foo foo(Foo val) { return fooData = val; }
It is technically possible to forbid the rewrite on lvalue returns like the foo above, however this would complicate the rewriting logic and introduce an inconsistancy in usage. This inconsistancy can be demonstrated:
import std.stdio;

class Foo  { int baz; }
struct Bar { int baz; }

Foo fooData;
Foo foo() { return fooData; }
Foo foo(Foo val) { return fooData = 5; }
Bar barData;
Bar bar() { return barData; }
Bar bar(Bar val) { return barData = 5; }

void main()
{
    fooData = new Foo();
    fooData.baz = 0;
    barData.baz = 0;
    foo.baz = 42;
    bar.baz = 42;
    writefln("%d",fooData.baz); // prints 42, foo's setter was not called.
    writefln("%d",barData.baz); // prints 5,  bar's setter was called.
}
Another possible problem is that code from before the change will still compile but change in meaning. However, this is not a severe problem as it turns out. Consider:
import std.stdio;

class C
{
    int a;
}

C barData;
C bar() { return barData; }
C bar(C val) { ... }

void main()
{
    barData = new C();
    barData.a = 0;
    bar.a = 42; // returns a reference type, not a value type.
    writefln("%d",barData.a); // prints ???
}
This program may change meaning depending on what the contents of "C bar(C val) {...}" are. However, users of bar have always had the option to write
C baz = bar;
baz.a = 42;
// Modify baz some more.
bar = baz;
This means that any properties written assuming that the expression rewrite doesn't happen is likely to be incorrect and buggy since the caller can do the rewrite by hand.

Another solvable issue worth mentioning is when there are side effects to the left of the property in the expression:

s[i++].prop.val = 3;
can be incorrectly rewritten as:
auto t = s[i++].prop;
t.val = 3;
s[i++].prop = t; // Incorrect, i++ is executed twice.
But this can be correctly written like so:
auto t1 = i;
auto t2 = s[t1].prop;
t2.val = 3;
s[t1].prop = t2;
t1++;
i = t1;
The overarching principle in this rewrite is that any side effects in an expression are removed and performed on temporary variables unless the compiler can prove that it doesn't need to do so. The treatment on i above is a bit severe, but only because it might be a property too! If i is not a property, but an int or size_t as usual, then the code can be simplified by optimizations:
auto t = s[i].prop;
t.val = 3;
s[i++].prop = t;

Examples of expression rewriting:

// -- The basics: --
a.b.c = 3; // b is a property

// becomes
auto t = a.b;
t.c = 3;
a.b = t;

// -- Deeper nesting: --
a.b.c.d = 3; // b and c are properties.

// becomes
auto t2 = a.b;
auto t1 = t2.c;
t1.d = 3;
t2.c = t1;
a.b = t2;

// -- Non-assignment mutations (++,--,+=,-=,*=,etc): --
a.b++; // b is a property.

// becomes
auto t = a.b;
t++;
a.b = t;

// -- More non-assignment mutations: --
a.b.c += 42; // b and c are properties

// becomes
auto t2 = a.b;
auto t1 = t2.c;
t1 += 42;
t2.c = t1;
a.b = t2;

// -- Complicated lvalues: --
s[s.length ++] = foo;

// becomes
auto t = s.length;
t++;
s[(s.length = t)] = foo;

// -- Unary Expressions: --
class Foo
{
   int count(){ return cnt++; }
}

Foo aFoo = new Foo;
int i = aFoo.count++;

// becomes
auto t = aFoo.count;
t++;
int i = (aFoo.count = t);

// -- Property's expression has side effects: --
s[i++].prop++; // i may or may not be a property and prop is a property.

// WRONG:
auto t = s[i++].prop;
t;
s[i++].prop = t;

// Correct:
auto t1 = i;
auto t2 = s[t1].prop;
t2.val = 3;
s[t1].prop = t2;
t1++;
i = t1;

// -- Torture testing and madness: --
// Everything is a property!!11
a.d[a.b.c++].c *= (p.q++) * (a.b.e = 3);

// Becomes
auto t1 = a;
auto t2 = t1.d;   // a.d[...]
auto t3 = t1.b;   // a.b.c++
auto t4 = t3.c;   // a.b.c++
auto t5 = t2[t4]; // a.d[a.b.c++]
auto t6 = t5.c;   // a.d[a.b.c++].c

// (p.q++)
auto t7 = p;
auto t8 = t7.q;

// (a.b.e = 3)
auto t9 = a; // Must be done again since it appears twice in the expression.
auto t10 = t9.b;

// Do not make a new temporary for .c;
// Explicit assignments call only write properties.  They do not read.
t6 *= t8 * (t10.e = 3);

// (a.b.e = 3)
// t9.b = t10; // Multiple assignments to b are refused. Already done at bottom.
// a = t9; // Multiple assignments to a are refused.  Already done at bottom.

// a.d[a.b.c++].c = ...
t5.c = t6;

// a.d[...]
t2[t4] = t5;
t1.d = t2;
// a = t1; // Multiple assignments to a are refused.  Already done at bottom.

// The opPostInc expressions must be done last.
// (p.q++)
t8++;
t7.q = t8;
p = t7;

// (a.b.c++)
t4++;
t3.c = t4;
t1.b = t3;
a = t1;
The last example has been tested.

Advantages:

  • Solves (1) and (2). By making the rewrite recognize not just properties but also opIndex, this can solve some issues with opIndex and opIndexAssign overloading as well.
  • Requires no new syntax. If explicit syntax IS used, then this is still required to solve (1) and (2) in a complete manner.
Disadvantages:
  • Breaks backwards compatibility, but only in cases that become errors or were likely bugs to begin with.

Explicit Property Syntax    

One of the ways to solve some of the problems with D's current property implementation is to create a new property implementation with explicit syntax. This syntax should unambiguously tell the compiler that the declarations in question are properties, not functions. The old implementation of properties ( omissible parentheses) may or may not be removed. Below are some advantages and disclaimers regarding property syntax.

Property syntax advantages in general:

  • It becomes easy to flag "a.b = 3;" as an error when 'a' is some property that returns a value type such as a struct or int. Thus (2) is solved, but only partially.
  • It allows IDEs to determine what variables are properties as opposed to functions or fields. (3)
  • It is clear that the getters and setters should not be called with parentheses. This helps with the noun and verb convention as well as ensuring that the properties can be migrated back to fields later. This solves problem (4).
  • The delegate ambiguity is resolved. (5)
  • Addition of this feature does not break the backwards compatibility of D. The removal of omissible parentheses does. Note that explicit properties and omissible parentheses may coexist peacefully.
  • It is possible to give properties optional backing storage when they are declared. This helps DRY in a common case where properties access the state of some similarly named variable:
int fooData;
int foo()      { return fooData; }     // fooData is repeated.
int foo(int v) { return fooData = v; } // fooData is repeated.  Again.

What property syntax doesn't solve:

  • "array.length()++;". There are some suggestions that can solve this, but usually at the expense of exhaustively defining operator overloads. Also, defining a proxy struct that defines opPostInc() and is returned from array.length() could potentially allow this to work, but again with the downside of exhaustively defining overloads. (1) is not solved by syntax alone.
  • "a.b = 3;": Making struct value returns work as expected. If a is a property that returns some struct, then "a.b = 3;" can be safely assumed to be an error without limiting the expressive power of functions returning rvalues. However, to make this call a's setter, some manner of property call rewriting is needed.

Properties vs Properties:

Explicit properties and omissible parentheses type properties are not exclusive to each other. Something may be defined as a function or a property, but not both at the same time. This means it is never ambiguous as to whether a function or a property is being called. Indeed, it may be useful to have them both be included in D.


Numerous suggestions have been proposed for property syntax. They tend to fall into one of two categories: defining properties as an attribute of a function, or defining properties as their own declaration complete with scope and members. Some known suggestions are given below.

Properties as a declaration    

Advantages:

  • Given that these properties have their own scope, they have the potential to intercept and override operators on the thing they are accessing. Such properties can also be populated with fields, functions, and other properties.
  • Type consolidation. In D's current property syntax, it is necessary to repeat the property's type: once for the getter's return, once for the setter's return, and another time for the setter's argument. With a property declaration, it is possible to define the type just once in the property's header and never again in the getter or setter.
  • It is remotely possible, with these types of syntax, to solve problems (1) and (2) without doing expression rewriting. This would be accomplished with operator overloads in the property's scope. Unfortunately, this has a couple downsides: it requires an enormous amount of boiler-plate code and would leave the opIndex/opIndexAssign lvalue-ness problem unresolved.
Disadvantage:
  • This tends to introduces two levels of nesting for executable parts of each property. Property-as-attribute solutions tend to do this with just one.

C# style    

int foo
{
    get { return fooData; }
    set { return fooData = value; }

    // Optionally, other things can be allowed into foo's scope:
    int opAddAssign(int v) { return fooData += v; }
    int fooData;
}

// or

int foo
{
    get { return fooData; }
    set(v) { return fooData = v; }
}

// or

int foo
{
    // This one has optional backing-storage.
    get { return foo; }
    set(v) { return foo = v; }
}

// or

int foo
{
    // Backing storage and less keyword usage.
    out { return foo; }
    in(v) { return foo = v; }
}

The main disadvantage to this one when compared to the other declaration type solutions is that it requires two keywords: get and set. The last variation did not, however, have this disadvantage. Also, get and set are context dependant, so it is possible for the compiler to allow their usage elsewhere. Still, syntax highlighting a context dependant keyword can sometimes fail, and other 3rd party tools might have a problem with it.

Keyworded    

A keyword (such as "property" in the below example) is used to distinguish a property declaration.

class Foo{
    public:
    int bar(int ix); //helper function

    property data{
        private:
        int data;

        public:
        int opReturn(){  //function used to return a value (may be called something else)
            return data; //notice that this function also define the type of the property
        }

        char[] opCast(){ //convert to string
            return printf("Property data is %i", data);
        }
        int opAssign(int v){ //assign a value
            data = v;
            return v;
        }
        int opIndex(int ix){ //example of some other operator
            return bar(ix);
        }
    }
}

Properties as a function attribute    

Advantage:

  • Given a property attribute solution, a property-declaration solution can possibly be produced using templates and nested structs/classes. The result may not look pretty though:
template Property( T, string name, string code )
{
    ...
}

mixin Property!(int,"foo",`
    get { return fooData; }
    set(v) { return fooData = v; }
`);

// This, of course, assumes that there is such a Property template that bothers
// to rewrite the code and that dmd doesn't choke on the compile-time string
// processing.

opGet_XX and opSet_XX    

int opGet_foo() { return fooData; }
int opSet_foo(int v) { return fooData = v; }

// USAGE:
foo = 42;
writefln("%d",foo); // prints 42

The idea is to rewrite "foo = 42;" into "opSet_foo(42);".

This syntax has a difficult technical limitation: when another field in the same scope as the property is defined with the same name, then it is ambiguous as to which one is called:

int foo;
int opGet_foo() { return fooData; }

writefln("%d",foo); // Which foo?
This can be reported by the compiler as an error, but only by doing a symbol lookup on both opSet_foo and foo. The compiler currently does not have to do such extra symbol lookups.

getXx and setXx    

This is very similar to the suggestion above, and inherits the same ambiguity difficulties.

int getFoo() { return fooData; }
int setFoo(int v) { return fooData = v; }

// USAGE:
foo = 42;
writefln("%d",foo); // prints 42
writefln("%d",Foo); // prints 42
writefln("%d",fOO); // Error: fOO undefined.

This is notable different from the previous suggestion due to making the first character of the property's identifier be case insensitive. This is mostly to allow flexibility in naming convention. This has been critiqued as being inconsistent with the rest of the language since there are no other instances of case-insensitivity in D.

Walter's syntax    

int foo { return fooData; }
int foo=(int v) { return fooData = v; }

// USAGE:
foo = 42;
writefln("%d",foo); // prints 42

The only problem is when a declaration but not definition is desired:

int foo;
However, that defines a field. So this hack is used:
int foo{}

This syntax was mostly received poorly when it appeared on the newsgroup.

xX.get and xX.set    

int foo.get() { return fooData; }
int foo.set(int v) { return fooData = v; }

// USAGE:
foo = 42;
writefln("%d",foo); // prints 42

Keyword solutions    

These are solutions where a keyword is used to distinguish functions as being strictly properties. The keyword can be a new keyword, a recycled keyword, or something keyword-like.

These solutions have the advantage that they can be applied as blocks and thus reduce the duplication of property annotation, which is good for DRY.

// Define 3 properties with 1 usage of "property"
// Any keyword or keyword-alike can be used, it doesn't have to be "property".
property
{
    int foo() { return fooData; }
    int foo(int v) { return fooData = v; }

    int bar() { return barData; }
    int bar(int v) { return barData = v; }

    int baz() { return bazData; }
    int baz(int v) { return bazData = v; }
}

in and out    

The return value of getters has "out" attached. The return value of setters has "in" attached.

out int foo() { return fooData; }
in int foo(int v) { return fooData = v; }

It has been noted that this syntax can be confusing when placed in close proximity to the other usages of "in" and "out":

out int foo()
out (result) { assert(result > 3); }
body { return something; }

in void foo(int v)
in { assert(v > 3); }
body { something = v; }

This also doesn't work as well as a block attribute, since it only allows getters to be grouped with getters and setters to be grouped with setters.

On the upside, it requires no new keywords.

property    

property int foo() { return fooData; }
property int foo(int v) { return fooData = v; }

property
{
    int bar() { return barData; }
    int bar(int v) { return barData = v; }
}

property:
int baz() { return bazData; }
int baz(int v) { return bazData = v; }

@property    

@property int foo() { return fooData; }
@property int foo(int v) { return fooData = v; }

This solution depends on the implementation of another feature: annotations.

It carries the advantage that property does not have to be a keyword, and can thus be used as an identifier elsewhere in code. Other keywords can also be phased out using the annotations feature.

Migration flow for various properties    

These tables describes which migrations of declarations will work. The first describes the migration possibilities in D as it currently stands. The second describes the migration possibilities after expression rewriting and explicit property syntax have been introduced.

Terminology:

  • NA Fields are Non-Addressable fields. The address-of operator (&field) cannot be used on them. All fields in SafeD are of this type.
  • PropFuncs are property-functions. All functions currently in D with 0 or 1 arguments are property getters and setters respectively.
  • Functions in the tables below are functions in the strict sense. A call to one of these MUST be done with parentheses. Any function in D currently written with 0 or 1 arguments will not fall into this category, as there is no way to enforce the use of parentheses. D functions with 2 or more arguments fall into this category.
   To   
  FieldsNA FieldsPropFuncsFunctions
 FieldsYes Mostly* Sometimes** No
From NA FieldsYes Yes Sometimes** No
 PropFuncsNo No Yes No
 FunctionsNo No No Yes

* Fields can be migrated to non-addressable fields in most cases except for, of course, addressing. It is possible for user code to create a pointer to one, but impossible to create a pointer to the other.

** The field types can be migrated to property-functions, but only sometimes. This is because if the field starts as a value type (such as an int, float, or struct) then it cannot be migrated to a property-function without breaking existing user code. Worst yet, the kind of broken code that results may compile fine but require hours of debugging.

The terminology in this second table is the same as the first. Notably, properties have been added, since explicit properties are assumed to exist in D for this table.

    To   
  FieldsNA FieldsPropertiesPropFuncsFunctions
 FieldsYes Mostly* Mostly* Mostly* No
 NA FieldsYes Yes Yes Yes No
From PropertiesSometimes** Sometimes** Yes Yes No
 PropFuncsNo No No Yes No
 FunctionsNo No No No Yes

* Fields can be migrated to non-addressable fields and property-alikes, with the exception of when their address is taken.

** Properties can not always be migrated to fields because they may be in a class and the user might inherit from the class and override the property. They can be migrated back to fields when they are a member of a struct, a final class, or are marked final.

Analysis:

  • Going from the first table to the second, property-functions gained a promotion in what could be migrated to them. There are no longer corner-cases where value types (such as ints, floats, and structs) are incapable of being migrated to a property or property-function.
  • Property-functions are a one-way road. Fields and properties can be migrated to them, but it is not possible to get back or migrate from property-functions to anything else. This is because property-functions may or may not be invoked with parentheses, a characteristic that is lacking from every other type of member.
  • Property-functions and functions are exclusive to each other when it comes to the 0 or 1 argument varieties. Either omissible parentheses are kept, or everything that was once a property-function becomes a strict function. This enivitability can be avoided by adding an annotation for functions that makes the distinction explicit, though such a thing would have little or no benefits.
  • Functions cannot be migrated to or from anything without breaking user code. Anything that starts life off as a function is quite frozen that way.

PropertyCombo    

It is possible to have omissible parentheses, lvalue expression rewriting, and explicit properties implemented all at the same time. This solves all of the problems while retaining the advantages of omissible parentheses.

Advantages: Everything mentioned directly above.

Disadvantages:

  • Of all the solutions to the current property problems, this is the most difficult to implement. This is because it entails the difficulty of implementing property expression rewriting plus the difficulty of implementing explicit property syntax, all while asserting that D compilers must support omissible parentheses. Then again, omissible parentheses aren't that hard to support, so this might not be more difficult than any other solution that solves all of the problems.
  • Some programmers might still complain that omitting parentheses from function calls or writing things like the below example is sloppy programming, and that they don't want to ever deal with such code written by other D programmers.
import std.stdio;

void printNumber(int val) { writefln("%d",val); }

void main()
{
    printNumber = 42;
}
  • It is possible to implement a property two different ways. This may be confusing to new D users.

Messages    

Add your comments here...

  • The example "writefln = 42" has not compiled for a while. It should be replaced by by a more general example. (such as "foo = 42", "func = 42", "free_func = 42" or "global_func = 42". (Got it. --ChadJoan) Or change it to writefln = "hi" which still compiles, I believe.
  • Omissible parentheses are also a part of D's template syntax: i.e. myTemplate!int vs. myTemplate!(int)
(Put it under "Redeeming qualities of D's current syntax". --ChadJoan)

  • The noun-verb section should include a section on the use of context/usage to disambiguate meaning. See http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=94924 for the relevant forum thread.
  • "the properties can be migrated back to fields later". This is false, as a property cannot be migrated back if a sub-class (which may exist in 3rd party code) which has over-overrided the property exists. (Check. --ChadJoan)
  • Problem (3) should mention 'ditto' D docs as a mitigating feature.
  • Problem (4) should contain all three calling options: (with or without () or either-or). During the forum discussions there were some very clear examples of either-or being correct, (std.string.split IIRC) (Done. --ChadJoan)
  • Problem (5) The delegate ambiguity contains a bad example of the problem. The actual problem isn't a zero-arg delegate returned from a zero-arg function. The issue is that a.b(5) will never be re-written a.b()(5) currently. This is actually a variant on (1) and (2). The current (5) should be merged with (1) or (2), and then limited to the double zero-arg corner case. There's also a solution to the ambiguity when limited to semantic rewriting discussed briefly here: http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=94932
  • BTW ref return properties don't work right yet either.
-- RobertJacques?

* The noun-verb section should include a section on the use of context/usage to 
disambiguate meaning. See 
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=94924
for the relevant forum thread.
Roger. I'll write something about it when I get more spare time. (done)

* Problem (3) should mention 'ditto' D docs as a mitigating feature.
The 'ditto' feature can be used to group getters and setters together in documentation. It can also be used to group overloads of a function, similar definitions, etc. So, how is this used to help with properties in a way that is unique to properties?

* Problem (5) The delegate ambiguity contains a bad example of the problem. 
The actual problem isn't a zero-arg delegate returned from a zero-arg function. 
The issue is that a.b(5) will never be re-written a.b()(5) currently. This is actually 
a variant on (1) and (2). The current (5) should be merged with (1) or (2), and then 
limited to the double zero-arg corner case. There's also a solution to the ambiguity 
when limited to semantic rewriting discussed briefly here: 
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=94932
I missed that one about a.b(5) vs a.b()(5) and function hijacking. As far as I can tell, the given NG post suggests that dmd should be able to figure things out when they aren't ambiguous, but currently doesn't. Thus the ambiguity is more general than I wrote AND there is a dmd bug to exacerbate it all.

I'm not sure I agree about merging the zero-arg-void-return case into (1) and (2), since it cannot be solved by semantic rewriting and is completely solved with explicit syntax, which is entirely unlike (1) and (2). Also it has little or nothing to do with lvalueness, and everything to do with syntactic ambiguities. If I'm missing something here let me know. I'm more inclined to expand (5) such that it mentions the ambiguity in the general case and has links to the dmd bugs. Please respond with more information. Otherwise, I'll expand (5) when I get some more time.

* BTW ref return properties don't work right yet either.
I suppose you're referring to the backwards compatibility disadvantage for semantic rewriting. Confirmed and noted.

Poster above me, thank you for the high quality critique.

-- ChadJoan


* Problem (3) should mention 'ditto' D docs as a mitigating feature.
The 'ditto' feature can be used to group getters and setters together in documentation.  
It can also be used to group overloads of a function, similar definitions, etc.  
So, how is this used to help with properties in a way that is unique to properties? 
It doesn't help in a way unique to properties, it helps in a way common to the rest of D. Hmm... I was thinking only about documentation, not IDE's, etc. Thinking out load: Today an IDE can identify possible getter and setter functions, it just doesn't know if it's a property or not. If a getter/setter pair is linked with 'ditto' in the Ddocs, then the IDE might be justified in guessing that those are really properties. Wouldn't work for single gets/sets and it would have false positive / false negative rates. However, these rates would be about the same as a human skimming the docs. A weak solution, but I honestly don't know if this would be good enough. I don't use C# on a regular basis so I'm not familiar with the property support in VS. I think a (link to a) screen shot or description would be a good motivator and demonstration of the importance of this point. (Added screenshot. I used sharpdevelop because I'm cheap and it works. Didn't bother linking since it's a measly 32 kB. --ChadJoan)

A clarification: I think zero-arg-void-return case deserves it's own point, seperate from the more general case (which may or may not be joined with points 1 and/or 2) as the ZAVR case, at best, results in a compilier-time error without an explicit property syntax.

* BTW ref return properties don't work right yet either.
I suppose you're referring to the backwards compatibility disadvantage for semantic rewriting.  Confirmed and noted.
Actually, I was referring to the problem of:
class A {
    int _x;
    ref int x() { return _x; }
}
    
void main(string[] args) {
   A a = new A();
   a.x = 5;  // does not compile
   a.x() = 5;// compiles
}
Though backwards compatibility is also an issue. Generally, semantic rewriting makes invalid/non-working code valid, so (I think) backward compatibility is less of an issue, and back-porting should mostly generate easy to find/fix compile time errors. Well, except for the a.b.c problem. Hmm... It would probably be a good idea to list the silently accepted, but logically invalid cases (like a.b.c) as part of back-porting / dual D1/D2 projects (unittests should help prevent this, but you never know how good they are). Similarly, removing ommitable parenthesis (besides breaking a bunch of existing code) would make it very hard for projects to cleanly support both D1 and D2. (i.e. DFL, Derelect, etc)

-- RobertJacques?

"classinfo" property    

All classes also have a .classinfo property. Information can be found under Phobos:object.

FAQ    

Member functions treated as fields    

"Properties are member functions that can by syntactically treated as if they were fields."

Q: By the example it seems as though all member functions of a struct/class can be treated as properties. Is this the case? I don't see any specific qualification that determines if a member function is a property or not.

A: I think the only restriction is that the member function has to take zero or one argument. If it takes zero arguments you can always use it like a propertly rvalue; if it takes one argument then you can always use it like a property lvalue.

Note that this property syntax extends to ordinary functions as well:

int gimme_five() { return 5; }
...
int seven = gimme_five + 2;

Why doesn't "array.length++" work?    

Q: The documentation says properties cannot be the lvalue of op=, ++, --. Does it mean I cannot do object.count++ ? It would just mean "object.count(object.count()+1)". -- AlvaroSegura?

A: I think there's a reason for this. A common mistake is to try to do "s.length++;". I know what I want the compiler to do, but D's designer is afraid that someone will try something like "s[s.length ++] = foo;" which would have an undefined result. -- JustinCalvarese

A: object.count(object.count() + 1) is shown the reason of prohibiting properties to be the lvalue. the expression has side-effect. I think you didn't want it in some case, but you can't find out, if compiler acquiesce.

class Foo
{
   int count(){ return cnt++; }
}

Foo aFoo = new Foo
int i = aFoo.count++; //i = ?

Links    

Corresponding page in the D Specification
FrontPage | News | TestPage | MessageBoard | Search | Contributors | Folders | Index | Help | Preferences | Edit

Edit text of this page (date of last change: August 25, 2009 0:34 (diff))