Difference between revisions of "Property Discussion Wrap-up"

From D Wiki
Jump to: navigation, search
(Optional parentheses)
 
(15 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
This is a wrap-up of the property discussion which happened around 25. January 2013. There have been some discussion before and there might be more in the future :-)
 
This is a wrap-up of the property discussion which happened around 25. January 2013. There have been some discussion before and there might be more in the future :-)
  
Except from off-topic, meta and personal discussions which are not summarized on this page these two issues were discussed:
+
Except from off-topic, meta and personal discussions which are not summarized on this page these issues were discussed:
  
 
== Link to discussion ==
 
== Link to discussion ==
 
If you've got too much time, the original discussions can be read here:
 
If you've got too much time, the original discussions can be read here:
  
* http://forum.dlang.org/thread/ceukykobasewoexsrveb@forum.dlang.org
+
* http://forum.dlang.org/post/ceukykobasewoexsrveb@forum.dlang.org
* http://forum.dlang.org/thread/kdqrnl$13ft$1@digitalmars.com
+
* http://forum.dlang.org/post/kdqrnl$13ft$1@digitalmars.com
  
DIPS:
+
== DIPS ==
  
 
Current
 
Current
 
* http://wiki.dlang.org/DIPs
 
* http://wiki.dlang.org/DIPs
  
Superceded
+
Superseded
 
* http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs
 
* http://www.prowiki.org/wiki4d/wiki.cgi?LanguageDevel/DIPs
  
 
== Property ==
 
== Property ==
  
* Everyone agrees that @property doesn't work well right now. People disagree what's the actual problem with @property. Some people where arguing to remove @property completely. It's not clear if those where advertising making normal functions callable like @properties or removing @property without replacement.
+
* Everyone agrees that @property doesn't work well right now. People disagree what's the actual problem with @property. Some people were arguing to remove @property completely. It's not clear if those were advertising making normal functions callable like @properties or removing @property without replacement.
  
 
=== Why @property ===
 
=== Why @property ===
As there were suggestions removing @property completely we should think about what problem @properties should solve:
+
As there were suggestions of removing @property completely we should think about what problem @properties should solve:
  
 
* Properties are a replacement for fields
 
* Properties are a replacement for fields
Line 63: Line 63:
  
  
Now consider how to implement the above if properties where completely removed, as proposed. The D1 solution allowed assignment syntax for normal functions. So the above example could work exactly the same way, but <code>@property</code> wouldn't be used. This is a poor solution for properties as it allows to write nonsensical code:
+
Now consider how to implement the above if properties were completely removed, as proposed. The D1 solution allowed assignment syntax for normal functions. So the above example could work exactly the same way, but <code>@property</code> wouldn't be used. This is a poor solution for properties as it allows to write nonsensical code:
  
 
<syntaxhighlight lang="D">
 
<syntaxhighlight lang="D">
Line 74: Line 74:
  
 
Note that this is in '''no way related''' to the optional parentheses discussion: You can have parentheses, a @property implementation and still disallow code like <code>writeln = 42;</code>
 
Note that this is in '''no way related''' to the optional parentheses discussion: You can have parentheses, a @property implementation and still disallow code like <code>writeln = 42;</code>
 +
 +
==== Breakage when removing @property ====
 +
<syntaxhighlight lang="D">
 +
//In druntime's object_.d AssociativeArray has:
 +
@property size_t length() { return _aaLen(p); } //that can't be rewritten as a field, so without @property it must be a function
 +
 +
//Right now:
 +
assert(is(typeof([int][int].length) == size_t);
 +
//Without @property
 +
assert(is(typeof([int][int].length) == size_t function());
 +
</syntaxhighlight>
  
 
=== Cons ===
 
=== Cons ===
Line 82: Line 93:
  
 
=== Property declaration syntax ===
 
=== Property declaration syntax ===
Some new syntaxes have bee proposed for property declaration:
+
Some new syntaxes have been proposed for property declaration:
  
 
* Could make declaring properties less verbose
 
* Could make declaring properties less verbose
Line 111: Line 122:
 
* How do you disambiguate property functions when they're free functions which conflict?
 
* How do you disambiguate property functions when they're free functions which conflict?
 
** Normal UFCS functions can be force called by using their fully qualified name. That's not possible for properties if function call syntax is disallowed?
 
** Normal UFCS functions can be force called by using their fully qualified name. That's not possible for properties if function call syntax is disallowed?
 +
* How many parameters are allowed for property functions?
 +
** Are default parameters allowed?
 +
** Especially consider the example about __FILE__ and __LINE__
 +
* Are templated properties allowed?
 +
** The access syntax for properties doesn't allow providing types
  
=== Proposals ===
+
==== Code examples ====
==== Proposal 1 ====
+
===== __LINE__ / __FILE__ =====
# @property functions can't be called with parentheses: <code>x._property()</code> is illegal! (Normal functions are explicitly not affected by this rule!)
 
## The only exception to this rule: If the returned value can be called (delegate, function pointer, opCall), then <code>x._property()</code> calls the returned value! It essentially behaves exactly as a field would.
 
# Getting a reference is illegal: <code>&x._property</code> does not compile.
 
## Rationale: Returning a reference to a temporary is not the same as taking the address of a field and is therefore confusing and dangerous in generic code. A real pointer could be returned if the property getter returned a value by ref. But then it would only work for some properties, could potentially bypass setters, would break if a setter was added later...
 
# To detect if something is a property or a field, use <code>__traits(isProperty, x._property)</code>! Do not use any tricks in user code to detect if something is a property or field.
 
# The getter/setter function can be obtained via __trais: <code>__traits(propertyGetter, x._property)</code> <code>__traits(propertySetter, x._property)</code>
 
## The function type'''s''' can be obtained like this: <code>typeof(__traits(propertyGetter, x._property)) == int delegate()</code>
 
# The type of the '''returned''' value is obtained with typeof: <code>typeof(x._property) == int</code>
 
# The compiler uses semantic rewriting to make @property access as similar as field access as possible.
 
# Property functions cannot return ref values (as this could bypass the setter)
 
# The setter may take it's parameter by ref.
 
# The <code>-property</code> compiler '''switch''' is removed
 
# @property on free functions
 
# A setters parameter type and the corresponding getters return type must match
 
## Two categories: UFCS properties and global properties (see example)
 
 
 
 
<syntaxhighlight lang="D">
 
<syntaxhighlight lang="D">
struct Type
+
//Currently valid and existing code
{
+
public @property string userId(string file = __FILE__, size_t
    @property void native(int);
+
line = __LINE__)
}
 
 
 
//Global properties:
 
//returns void, has 1 parameter --> global setter
 
@property void external1(int);
 
//returns !void, has 0 parameters --> global getter
 
@property int external1();
 
 
 
//Used like a global variable:
 
void a()
 
{
 
    external1 = 41;
 
    // 41.external1; is
 
    // invalid, a getter can't return void (void + 1 args is always a global variable property)
 
    external1++;
 
    assert(external1 == 42);
 
}
 
 
 
//UFCS properties:
 
//returns void, has 2 parameters --> global setter
 
@property void external2(Type, int);
 
//returns !void, has 1 parameters --> global getter
 
@property int external2(Type);
 
 
 
//external2 can only be called on a Type instance.
 
//calling it manually "external2(Type(), 42)" is invalid
 
 
 
//Used like a property/field declared on Type:
 
void a()
 
 
{
 
{
     Type t;
+
     if(true)
    t.external2 = 41;
+
        throw Exception("", file, line);
    t.external2++;
 
    assert(t.external2 == 42);
 
    //all "t.external1" combinations are invalid!
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
=== Proposals ===
===== Unified test suite =====
+
[[Property Discussion Proposal 1]]
<syntaxhighlight lang="D">
 
alias Deleg = int delegate();
 
 
 
struct Test
 
{
 
    int var;
 
   
 
    @property void native2(int param = 1337) { } //invalid. default param creates invalid case with no args which returns void
 
    @property void native(int) { } //OK
 
    @property int native() { return 42; } //OK
 
   
 
    @property void strange() { } //not allowed
 
   
 
    @property Deleg delegRet() { return () => 42; } //OK
 
}
 
 
@property void external1(int) { } //OK (Global setter)
 
@property int external1() { return 42; } //OK (Global getter)
 
 
 
@property void external2(Test, int) { } //OK (UFCS setter)
 
@property int external2(Test) { return 42; } //OK (UFCS getter)
 
 
void main()
 
{
 
    Test t;
 
    t.native = 42; //OK
 
    t.native(42); //invalid
 
    int a = t.native; //OK
 
    t.strange; //invalid (definition of strange already invalid)
 
   
 
    external1 = 42; //OK
 
    external1(42); //invalid
 
    int b = external1; //OK   
 
    42.external1; //invalid
 
   
 
    t.external2 = 42; //OK
 
    auto c = t.external2; //OK
 
    external2(t).external1; //invalid
 
   
 
    t.delegRet; //basically valid, could complain about statement has no effect if delegRet getter is pure
 
    t.delegRet(); //valid, gets delegate then calls it(but as the result
 
                  //of the delegate call isn't used the compiler could complain if the delegate is pure)
 
    t.delegRet()(); //invalid
 
   
 
    pragma(msg, is(typeof(t.var) == typeof(t.native))); //OK
 
   
 
    pragma(msg, typeof(external1)); //int
 
    import std.traits;
 
    // pragma(msg, typeof(ReturnType!(typeof(external1)))); // error (typeof(external1) returns int, not a function type)
 
   
 
    pragma(msg, typeof(&(t.delegRet))); //illegal, can't get address of temporary? (Not sure here)
 
    pragma(msg, typeof(t.delegRet));    //int delegate()
 
    pragma(msg, typeof(t.delegRet()));  //OK (is this valid D code in general?)
 
    pragma(msg, typeof(t.delegRet()())); //invalid
 
   
 
    pragma(msg, ReturnType!(typeof(t.delegRet))); //int
 
}
 
</syntaxhighlight>
 
 
 
==== Proposal 1.1 ====
 
# Like proposal 1, but instead of rule 7, returning ref values is allowed as long as there is not setter function.
 
 
 
==== Proposal 1.2 ====
 
This is the same as proposal 1 except that marking a function or method as @property turns it into a variable-like entity, such that to the outside world, it behaves ''exactly'' like a variable, and ''not'' like a function:
 
 
 
# Writing "&prop" is legal only if (1) func returns by ref, and (2) there is no setter method prop(T). It simply returns the address of the returned value. It does '''not''' return a function pointer to prop. (To the outside world, prop is a variable, not a function.)
 
# It is illegal to call a @property function using regular function-call syntax: if you have @property void ufcsProp(U, T) {...}, then it is illegal to write "ufcsProp(u,t)". Instead, you must write "u.ufcsProp = t".
 
# Assignment syntax f=x; is only valid if f is @property. It is illegal for non-@property f. Same goes for assignment-related operations like f++, f+=2, etc..
 
<!-- MERGED
 
* I think most of this could me merged to 1. On a quick look the only difference seems to be this proposal has less traits (can't access getter/setter function) and this allows &prop. This proposal also makes it clear that properties are treated like/variables fields. The first proposal doesn't state it as clearly, but this is also meant in #1. How about merging the generic part into #1 and only listing those differences here?
 
-->
 
 
 
=== Corner cases to consider ===
 
 
 
==== @property on free functions ====
 
<syntaxhighlight lang="D">
 
struct Type
 
{
 
    @property void native(int);
 
}
 
 
 
@property void external1(int);      // valid? (no assignment context)
 
@property void external2(Type, int); // valid? (extra parameter comparing to typical setter)
 
 
 
void main()
 
{
 
    Type t;
 
    t.native = 42;    // typical
 
    external1 = 42;  // allowed or not?
 
    42.external1;    // allowed or not?
 
    t.external2 = 42; // allowed or not?
 
}
 
</syntaxhighlight>
 
 
 
==== opDispatch and properties ====
 
<blockquote style="background-color: lightgrey; border: solid thin grey;">
 
If you make opDispatch @property, then you can't use it with normal functions, and if you don't make it @property, then it can't be used with
 
property functions. And you can't overload on @property, so opDispatch
 
is pretty much screwed with regards to properties at this point.
 
</blockquote>
 
  
 
== Optional parentheses ==
 
== Optional parentheses ==
Line 303: Line 171:
  
 
=== How those are (not!) related to properties ===
 
=== How those are (not!) related to properties ===
Both properties and optional parentheses are basically orthogonal. It's possible to have both, have none and have any mix of those.
+
Both properties and optional parentheses are basically orthogonal. It's possible to have both, have none, and have any mix of those.
  
 
The small issue where they interfere is code readability:
 
The small issue where they interfere is code readability:
Line 324: Line 192:
 
struct Test
 
struct Test
 
{
 
{
int var;
+
  int var;
 
 
@property void native(int param = 1337) { }
+
  @property void native(int param = 1337) { }
@property int native() { return 42; }
+
  @property int native() { return 42; }
 
          
 
          
        @property int both1(int a) { return a; }
+
  @property int both1(int a) { return a; }
  
        @property ref int both2() { return var; }
+
  @property ref int both2() { return var; }
 
 
@property void strange() { }
+
  @property void strange() { }
 
 
@property Deleg delegRet() { return () => 42; }
+
  @property Deleg delegRet() { return () => 42; }
 +
 
 +
  struct Inner
 +
  {
 +
      void func1() { }
 +
      void func2() const { }
 +
 
 +
      int a;
 +
  }
 +
 
 +
  Inner inner;
 +
 
 +
  @property Inner nested1() { return inner }
 +
  @property ref Inner nested2() { return inner }
 +
  @property ref const(Inner) nested3() { return inner }
 
}
 
}
 
   
 
   
Line 345: Line 227:
  
 
// @property void lotof(Test, int, int, int) { } // Error now
 
// @property void lotof(Test, int, int, int) { } // Error now
 +
 +
struct Dispatcher1
 +
{
 +
    void opDispatch(string arg)() { }
 +
}
 
   
 
   
 +
struct Dispatcher2
 +
{
 +
    @property void opDispatch(string arg)() { }
 +
}
 +
 
void main()
 
void main()
 
{
 
{
Line 370: Line 262:
 
     external2(t).external1;
 
     external2(t).external1;
 
 
 +
    t.nested1.func1();
 +
    t.nested1.func2();
 +
    t.nested1.a;
 +
    t.nested2.func1();
 +
    t.nested2.func2();
 +
    t.nested2.a;
 +
    t.nested3.func1();
 +
    t.nested3.func2();
 +
    t.nested3.a;
 +
 
     t.delegRet;
 
     t.delegRet;
 
     t.delegRet();
 
     t.delegRet();
Line 386: Line 288:
 
 
 
     pragma(msg, ReturnType!(typeof(t.delegRet)));
 
     pragma(msg, ReturnType!(typeof(t.delegRet)));
 +
 +
    Dispatcher1 disp1; Dispatcher2 disp2;
 +
    disp1.something;
 +
    disp1.something();
 +
    disp2.something;
 +
    disp2.something();
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
[[Category:Proposals]]

Latest revision as of 21:40, 20 May 2015

This is a wrap-up of the property discussion which happened around 25. January 2013. There have been some discussion before and there might be more in the future :-)

Except from off-topic, meta and personal discussions which are not summarized on this page these issues were discussed:

Link to discussion

If you've got too much time, the original discussions can be read here:

DIPS

Current

Superseded

Property

  • Everyone agrees that @property doesn't work well right now. People disagree what's the actual problem with @property. Some people were arguing to remove @property completely. It's not clear if those were advertising making normal functions callable like @properties or removing @property without replacement.

Why @property

As there were suggestions of removing @property completely we should think about what problem @properties should solve:

  • Properties are a replacement for fields
    • Unlike fields, they are polymorphic
    • As they are supposed to be used as fields, they are for data access and nothing else
    • They are especially not there to make function calls without parentheses. This is a different issue. To the developer properties are not functions, they're enhanced fields. The fact that they are implemented as functions is a implementation detail.
      • D as a systems programming language should still provide a way to detect if something is a property and to get the getter/setter. This shouldn't be visible in normal usage.
    • Replacing a field by a property should be transparent
  • Properties allow executing additional code. Checking the supplied value, firing events,...
    • As a property looks like data access, complexity should be kept constant.
  • Properties allow making a field readonly (public get, private/no set), readwrite(public get/set) or write-only (private/no get, public set).


An example for property usage:

struct A
{
    int _days;
    @property int weeks()
    {
        return _days/7;
    }

    @property void weeks(int value)
    {
        assert(value <= 42);
        _days = value*7;
    }
}

void use()
{
    A a;
    a.weeks = 3;
    a.weeks++;
    a.weeks -=2;
    typeof(a.weeks) b = a.weeks;
}


Now consider how to implement the above if properties were completely removed, as proposed. The D1 solution allowed assignment syntax for normal functions. So the above example could work exactly the same way, but @property wouldn't be used. This is a poor solution for properties as it allows to write nonsensical code:

void use()
{
    writeln = 42; //would call writeln(42);
    writeln += 1; //How would this even be implemented without @property?
}

Note that this is in no way related to the optional parentheses discussion: You can have parentheses, a @property implementation and still disallow code like writeln = 42;

Breakage when removing @property

//In druntime's object_.d AssociativeArray has:
@property size_t length() { return _aaLen(p); } //that can't be rewritten as a field, so without @property it must be a function

//Right now:
assert(is(typeof([int][int].length) == size_t);
//Without @property
assert(is(typeof([int][int].length) == size_t function());

Cons

(Please do not add implementation issues, only conceptual issues)

  • Some people find it difficult to decide if something is a property or a function
  • Some people think the additional @property attribute clutters the source code

Property declaration syntax

Some new syntaxes have been proposed for property declaration:

  • Could make declaring properties less verbose
  • Would break lots of code
    • Could be done without breakage with new attributes (@prop), but that's ugly
  • There's no need for a new syntax. Although the current syntax is verbose, it does not have real issues. The issues happen when using properties, not when declaring them.
    • The allowed function prototypes (ref) should be reviewed though and it should be made clear how they interact:


@property ref int prop();
@property int prop();
@property void prop(int value);
@property int prop(int value);
@property void prop(ref int value); //not as problematic

Implementation concerns

  • Can we get a reference to the property? What does &x.property_ mean?
  • How can we get the getter / setter functions? Do we need to get those?
  • What is the type of the property? Return type, setter function type or getter function type? How to get the other types?
  • What does x.property_++ do? (Semantic rewriting)
  • Is returning ref values from the getter OK?
  • Is taking ref values in the setter OK?
  • Should all properties be nothrow? pure? getter const?
    • Probably better as a rule for style guide.
  • Are UFCS properties possible? How do they work exactly?
  • How do you disambiguate property functions when they're free functions which conflict?
    • Normal UFCS functions can be force called by using their fully qualified name. That's not possible for properties if function call syntax is disallowed?
  • How many parameters are allowed for property functions?
    • Are default parameters allowed?
    • Especially consider the example about __FILE__ and __LINE__
  • Are templated properties allowed?
    • The access syntax for properties doesn't allow providing types

Code examples

__LINE__ / __FILE__
//Currently valid and existing code
public @property string userId(string file = __FILE__, size_t 
line = __LINE__)
{
    if(true)
        throw Exception("", file, line);
}

Proposals

Property Discussion Proposal 1

Optional parentheses

Pro

  • UFCS code looks nicer:
auto yesterday = 2.days.ago;

some!(e1).ufcs!(e2).chaining!(e3)
//instead of
some!(e1)().ufcs!(e2)().chaining!(e3)()

//We need a real world UFCS case demonstrating this.
//There are real world examples - even in that discussion thread - but I cant find them ;-)

Extra note

The same way "." can dereference pointer, it is unambiguous to automatically call functions on ".". This shortcut is unambiguous (even if some people may find it confusing when reading code).

// Only the last () is necessary in that case.
some!(e1).ufcs!(e2).chaining!(e3)()

Cons

  • Ambiguous / complicated if a function returns a delegate or similar (solvable with special case rules)
  • Complicates semantics for human reader of the code (see comments about readability in "How those are (not!) related")

How those are (not!) related to properties

Both properties and optional parentheses are basically orthogonal. It's possible to have both, have none, and have any mix of those.

The small issue where they interfere is code readability:

If optional parentheses are disallowed, it's easy to see that

auto var = new someClass();
auto x = var.something;

will always be either a field access or a property call. But according to property definition (as used by C# and other languages), properties should behave like fields (i.e. constant complexity, no side-effects,...). Therefore without optional parentheses it's possible to see if an access is actually a potentially heavy weight function call or data / property access.

Except from this, @property and optional parentheses are orthogonal.

Unified test suite

Aims to provide one source file that provides all use cases that work with current lax rules. Any proposal should give clear answer, which of them are allowed and how those should work.

alias Deleg = int delegate();

struct Test
{
   int var;
	
   @property void native(int param = 1337) { }
   @property int native() { return 42; }
        
   @property int both1(int a) { return a; }

   @property ref int both2() { return var; }
	
   @property void strange() { }
	
   @property Deleg delegRet() { return () => 42; }

   struct Inner
   {
       void func1() { }
       void func2() const { }

       int a;
   }

   Inner inner;

   @property Inner nested1() { return inner }
   @property ref Inner nested2() { return inner }
   @property ref const(Inner) nested3() { return inner }
}
 
@property void external1(int) { } 
@property int external1() { return 42; }

@property void external2(Test, int) { }
@property int external2(Test) { return 42; }

// @property void lotof(Test, int, int, int) { } // Error now

struct Dispatcher1
{
    void opDispatch(string arg)() { }
}
 
struct Dispatcher2
{
    @property void opDispatch(string arg)() { }
}

void main()
{
    Test t;
    t.native = 42;
    t.native;
    t.native(42);
    int a = t.native;
    // t.native++; // Error now
    // t.native += 42; // Error now
    t.strange;
    t.both1 = 42;
    // a = t.both1; // Error now
    t.both2 = 42;
    a = t.both2;
	
    external1 = 42;
    external1(42);
    int b = external1;	
    42.external1;
	
    t.external2 = 42;
    auto c = t.external2;
    external2(t).external1;
	
    t.nested1.func1();
    t.nested1.func2();
    t.nested1.a;
    t.nested2.func1();
    t.nested2.func2();
    t.nested2.a;
    t.nested3.func1();
    t.nested3.func2();
    t.nested3.a;

    t.delegRet;
    t.delegRet();
    t.delegRet()();
	
    pragma(msg, is(typeof(t.var) == typeof(t.native)));
	
    pragma(msg, typeof(external1));
    import std.traits;
    // pragma(msg, typeof(ReturnType!(typeof(external1)))); // Error now
	
    pragma(msg, typeof(&(t.delegRet)));
    pragma(msg, typeof(t.delegRet));
    pragma(msg, typeof(t.delegRet()));
    pragma(msg, typeof(t.delegRet()()));
	
    pragma(msg, ReturnType!(typeof(t.delegRet)));

    Dispatcher1 disp1; Dispatcher2 disp2;
    disp1.something;
    disp1.something();
    disp2.something;
    disp2.something();
}