Skip to content

Instantly share code, notes, and snippets.

@JinShil
Last active November 24, 2018 03:05
Show Gist options
  • Save JinShil/40db71a5e32fd29924e68863000e6de1 to your computer and use it in GitHub Desktop.
Save JinShil/40db71a5e32fd29924e68863000e6de1 to your computer and use it in GitHub Desktop.

Property Functions

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);
}

Exchange with Adam Ruppe

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

Changes required by this DIP

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.

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

Justification for @property

  • 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?

Rationale

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.

Why optional parentheses cannot fulfill the promise of the property abstraction

Anomaly 1

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.

Anomaly 2

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.

Anomaly 3

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.

The @property function implementation is incomplete

Invalid property function signatures

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
    { }
}

Ambiguities when returning a callable

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.

Typesafe variadic @property functions are not allowed

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.

Description

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment