Difference between revisions of "DIP23"
(→Rationale) |
(→Explicit @property annotations) |
||
Line 173: | Line 173: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | === "Read" properties with the @property annotation === |
Functions annotated with @property are subject to additional restrictions compared to regular functions. | Functions annotated with @property are subject to additional restrictions compared to regular functions. |
Revision as of 07:59, 3 February 2013
Contents
DIP23: Fixing properties redux
Title: | Fixing properties |
---|---|
DIP: | 23 |
Version: | 1 |
Status: | Draft |
Created: | 2013-02-02 |
Last Modified: | 2013-02-02 |
Author: | Andrei Alexandrescu and Walter Bright |
Links: |
Abstract
There has been significant debate about finalizing property implementation. This document attempts to provide a proposal of reasonable complexity along with checkable examples.
Forces:
- Break as little code as possible
- Avoid departing from the existing and intended syntax and semantics of properties
- Make economy of means (little or no new syntax to learn)
- Avoid embarrassing situations such as expressions with unexpressible types or no-op address-of operator (as is the case with C functions).
Description
Optional parens stay in
One can't discuss properties without also discussing optional parens. These obviate to some extent the need for properties (at least of the read-only kind) and make for potential ambiguities.
This proposal sustains that optional parentheses should stay in. That means, if a function or method may be called without arguments, the trailing parens may be omitted.
unittest
{
int a;
void fun1() { ++a; }
// will call fun
fun1;
assert(a == 1);
// Works with default arguments, too
void fun2(string s = "abc") { ++a; }
fun2;
assert(a == 2);
}
The same goes about methods:
unittest
{
int a;
struct S1 { void fun1() { ++a; } }
S1 s1;
// will call fun
s1.fun1;
assert(a == 1);
// Works with default arguments, too
struct S2 { void fun2(string s = "abc") { ++a; } }
S2 s2;
s2.fun2;
assert(a == 2);
}
However, that's not the case with function objects, delegate objects, or objects that implement the function call operator.
unittest
{
static int a;
static void fun1() { ++a; }
auto p1 = &fun1;
// Error: var has no effect in expression (p1)
p1;
assert(a == 0);
}
unittest
{
int a;
void fun1() { ++a; }
auto p1 = &fun1;
// Error: var has no effect in expression (p1)
p1;
}
unittest
{
static int a;
struct S1 { void opCall() { ++a; } }
S1 s1;
// Error: var has no effect in expression (s1) s1;
s1;
}
Taking the type of a symbol that may be used in a paren-less call results in the type of the returned object. THIS IS A CHANGE OF SEMANTICS.
unittest
{
int fun1() { return 42; }
static assert(is(typeof(fun1) == int));
}
To get the function type, one must apply the address-of operator.
unittest
{
int fun1() { return 42; }
static assert(is(typeof(&fun1) == int delegate()));
static int fun2() { return 42; }
static assert(is(typeof(&fun2) == int function()));
}
The same goes about member functions. THIS IS A CHANGE OF BEHAVIOR.
unittest
{
struct S1 { int fun() { return 42; } }
S1 s1;
assert(s1.fun == 42);
static assert(is(typeof(s1.fun) == int)); // currently fails
}
The basic motivation here is that "s1.fun" should not change type when under "typeof".
If a function returns a reference, then assignment through the paren-less call should work:
unittest
{
static int x;
ref int fun1() { return x; }
fun1 = 42;
assert(x == 42);
}
A function that returns an object that in turn supports a call with "()" will never automatically apply implicit parens to the returned object. Using either `fun` or `fun()` will return the callable entity. To invoke the callable entity immediately one must use `fun()()`.
unittest
{
static int x;
int function() fun1() { return () => 42; }
assert(is(typeof(fun1) == int function()));
assert(is(typeof(fun1()) == int function()));
assert(is(typeof(fun1()()) == int));
assert(fun1()() == 42);
}
"Read" properties with the @property annotation
Functions annotated with @property are subject to additional restrictions compared to regular functions.
In brief, the "()" operator may NEVER be applied EXPLICITLY to a function annotated with @property. THIS IS A CHANGE OF SEMANTICS.
unittest
{
@property int prop1() { return 42; }
assert(prop1 == 42);
static assert(is(typeof(prop1) == int));
static assert(!__traits(compiles, prop1()));
}
Applying the "()" to a property will simply apply it to the result of the property. THIS IS A CHANGE OF BEHAVIOR.
unittest
{
@property int function() prop1() { return () => 42; }
assert(prop1() == 42);
}
(Note: The @property annotation is not part of the function type, so it is impossible for a property to return a property.)
Write properties
In order to use the assignment operator "=" property-style, the @property annotation MUST be used.
The rule for allowing assignment with properties is simple.
1. If "foo" is a function that has the @property annotation AND takes exactly one parameter, then "foo = x" calls foo with argument x. Calling "foo(x)" is disallowed. The type of the expression "foo = x" is the type of foo's result.
unittest
{
@property void fun(int x) { assert(x == 42); }
fun = 42;
assert(is(typeof(fun = 42) == void));
}
2. If "foo" is a function that has the @property annotation AND takes exactly two parameters, then "x.foo = y" calls foo with arguments x and y. Calling "foo(x, y)" or "x.foo(y)" is disallowed.
unittest
{
@property double fun(int x, double y) { assert(x == 42 && y == 43); return y; }
42.fun = 43;
assert(is(typeof(42.fun = 43) == double));
}
3. If "foo" is a member function of a class or struct that has the @property annotation AND takes exactly one parameter (aside from the implicit parameter this), then "x.foo = y" calls x.foo with argument y.
unittest
{
struct S1
{
@property double fun(int x) { assert(x == 42); return 43; }
}
S1 s1;
s1.fun = 42;
assert((s1.fun = 42) == 43);
assert(is(typeof(s1.fun = 42) == double));
}
Talk
goes here
Copyright
This document has been placed in the Public Domain.