It seems there are a few fundamental problems with properties
- Optional parens
- Taking the address of a property with the ''&'' operator. According to Adam, the ''&'' operator should apply to the function not the return value.
- Need to consider UFCS
- Is property a function, an rvalue, or an lvalue?
- Taking the address of a ''ref @property''
Problem with current pull request
- Need to exclude rewrites where a ''ref @property'' function doesn't contain a setter. Or for that matter, any pair that does not contain a setter. But, how do I know if a setter exists? Update spec to say "rewrite only occurs if both a getter and setter exist".
- What happens if getter is ref, but setter is not. Or vise versa.
- Check unit tests from DIP23
- Add tests for UFCS
Another good property discussion
Unary assigment operators
import std.stdio;
struct MyInt
{
int m_value;
int m_copyCount;
this(this)
{
m_copyCount++;
}
int opBinary(string op)(int value)
{
return mixin("m_value" ~ op ~ "value");
}
MyInt opAssign(int value)
{
m_value = value;
return this;
}
}
struct S
{
MyInt field;
int setterCount;
int getterCount;
MyInt prop() @property
{
getterCount++;
return field;
}
MyInt prop(int value) @property
{
setterCount++;
return field = value;
}
}
S s;
int sideEffectCount;
S* getS()
{
sideEffectCount++;
return &s;
}
void main()
{
int b;
// b = s.prop++;
((auto ref _e1) => { auto tmp = _e1.prop(); _e1.prop(tmp + 1); return tmp;})(getS)();
writeln(b);
writeln(s.field.m_value);
writeln(s.setterCount);
writeln(s.getterCount);
writeln(s.field.m_copyCount);
writeln(sideEffectCount);
}
... about https://forum.dlang.org/post/yjdcfnsywahjhktnaykh@forum.dlang.org
<adam_d_ruppe> so i've tried this kind of thing before. you run into a few problems... at least my approach. returning some wrapper object that holds a reference to the original so it can forward overloaded operators to it hits lifetime troubles pretty easily
<adam_d_ruppe> and people's insistence on using type inference aggravates that
This DIP asserts that the problems associated with @property functions is caused by an incomplete design and implementation, not by a flaw in its fundamental purpose.
- Need to look into ''opDispatch'' issues: https://issues.dlang.org/show_bug.cgi?id=10698
- Need to consider what happens with
ref
returns: https://issues.dlang.org/show_bug.cgi?id=11120 - This doesn't work and may cause problems in the implementation if it's not fixed: https://issues.dlang.org/show_bug.cgi?id=2853
Optional parentheses is a failure: https://forum.dlang.org/post/mqvypowxlwxjewinngpx@forum.dlang.org
- Too many corner cases causes it become difficult to understand, implement, and test: https://forum.dlang.org/post/gxilrnjibsfnrenjyyls@forum.dlang.org
- If we add binary assignment operators, and we don't have @property, it will allow users to apply the binary assignment operators to functions when they don't intend them to. If we have @property, we allow users opt into whatever appropriate semantics they require without causing semantic collisions.
@properties are necessary: https://forum.dlang.org/post/mailman.687.1359054898.22503.digitalmars-d@puremagic.com
()
operator applies to the return value&
(address of) operator applies to the return value- Property functions cannot be called with
()
- Deprecate invalid property definitions: https://issues.dlang.org/show_bug.cgi?id=8161
- Member functions already have a default context argument, so can't be used as UFCS properties.
- Setters can't have default arguments: https://issues.dlang.org/show_bug.cgi?id=17474
- Setters can't return an lvalue? https://issues.dlang.org/show_bug.cgi?id=17474#c7
- dlang/dmd#6881 (comment)
- We are looking for simple and clear rules indeed. How about this: "Properties must have either exactly zero or one parameter. If present, the parameter cannot be defaulted." dlang/dmd#6881 (comment)
writeln = "Hello, World!";
is odd to say the least. https://issues.dlang.org/show_bug.cgi?id=17474#c9- What about variadics? https://issues.dlang.org/show_bug.cgi?id=10867
- Variadic array arguments should be allowed.
- Consider accessing packed data like the 565 color format.
- Also bitfields in microcontrollers
- How to distinguish if methods should participate in the lowering for binary or unary assignment operators.
- When something is explicitly attributed with
@property
then it can help in tooling by displaying properties differently than ordinary functions to make it easier to understand code at a glace.
void prop() @property; //invalid
int prop() @property; // getter
void prop(int x) @property; // setter
int prop(int x) @property; // setter or UFCS getter. Can be disambiguated by context (member or free function)?
void prop(int x, int y) @property; // UFCS setter
int prop(int x, int y) @property; //UFCS setter
int propt(A)(A...) @property; // variadics are invalid?
The DIP assumes that the rationale for encapsulating fields and other implementation details behind accessor methods with field-like semantics, herein designated the property absraction, is already well-understood and congruent with D's design philosophy, specifically modeling power, and modern convenience. Therefore, this DIP will not motivate the need for the property abstraction, but will motivate @property
functions as the preferred implementation of the property abstraction in D.
Optional parentheses causes ambiguities with generic code and callable objects.
void call(alias f, Args...)(Args args)
{
f(args);
}
void delegate() foo();
void delegate(int) bar(int x);
call!foo(); // Ambiguity: calls `foo`, return value of `foo`, or both?
call!bar(0); // Ambiguity: calls `bar`, return value of `bar`, or both?
While this could potentially be solved with rules to resolve the ambiguity, those rules quickly become too complicated, leaving users debugging their understanding of the language rather than their code.
Optional parentheses results in unintended semantic anomalies in the language.
import core.stdc.stdlib;
uint randomNumber()
{
return rand();
}
uint randomNumber(uint maximum)
{
return randomNumber() % maximum;
}
With optional parentheses, those two functions appear to the language as a getter and setter pair. That pair will permit oddities such as randomNumber = 255;
, and with the addition of binary assignment operators, randomNumber += 255
, though that is not the kind of usage the author intended.
Optional parentheses causes bad interplay with functions that have default properties. This was submitted as Issue 17471.
struct S
{
int* pInt;
ref int* not_a_property(string str = "Hello")
{
assert(str !is null);
return pInt;
}
}
void main() {
S s;
int x;
s.not_a_property = &x; // OK. `&x` is assigned to `prop`'s return value
s.not_a_property = null; // Assertion failure. `null` is passed as argument to `prop`
}
not_a_property
is not designated as a @property
function, but due the the optional parentheses feature in D, it can be called as if it is a property. If value
is null
the compiler passes value
as an argument to s.not_a_property
. Otherwise it assigns value
to s.not_a_property
's return value. This is due to the fact that there is no way for the author of not_a_property
to designate the intended usage, and both behaviors can be justified.
As articulated in issue 8161, The current implementation of @property
functions allows many function signatures to be designated as a @property
including void
getters and variadic setters:
struct S
{
// This signature has no utility in the property abstraction, but is currently allowed in the
// existing implementation.
void prop() @property { }
// A function signature with 2 parameters can be used as a property via UFCS, but the following signature
// actually has 3 parameters due to the implicit context pointer. This is not valid when used as a
// property, but is currently allowed in the existing implementation.
void prop(int x, int y) @property
{ }
}
Due to the optional parentheses feature in D, and the fact that @property
functions are still functions to the language, @property
functions can still be called with ()
. This creates an ambiguity about whether the ()
operator applies to the @property
function itself, or its return value.
import std.stdio;
struct S
{
void delegate() prop() @property
{
return () => writeln("delegate called!");
}
}
void main()
{
S s;
auto d1 = s.prop; // assigns return value of `prop` to d1
auto d2 = s.prop(); // assigns return value of `prop` to d2
s.prop()(); // calls the return value of `prop`, printing "delgate called!".
}
In the current implementation, although functions are designated with the @property
attribute, the language requires that they first be called as normal functions in order to call the return value. In other words, although the function is designated as a property, it doesn't actually behave like one.
As articulated in issue 10867, the current implementation does not allow typesafe variadic functions to be used as @property
functions.
struct S
{
void prop(int[] arr...) @property { }
}
void main()
{
S s;
// assigns a single array of length 1
// Error: properties can only have zero, one, or two parameter
s.prop = 1;
// assigns a single array of length 2
// Error: properties can only have zero, one, or two parameter
s.prop = [1, 3];
}
Typesafe variadic functions only take a single parameter, the array, which is a perfectly legitimate signature for a @property
function.
To avoid ambiguities and semantic anomalies, the language needs a way for authors to designate which functions can participate in the property abstraction The @property
attribute is already in widespread use in the D ecosystem, so it in a well-established position to serve that purpose; it just needs a more complete implementation.