DIP28

From D Wiki
Jump to: navigation, search
Title: Functions
DIP: 28
Version: 2
Status: Draft
Created: 2013-02-27
Last Modified: 2013-14-27
Author: Amaury SÉCHET
Links: DIP23, DIP27

Abstract

This DIP is the second of 3. It intend to define properties in D. Just like DIP27 it intend to be simple and avoid special cases, by defining properties as function that are called 100% of the time in 100% of the situations.

Rationale

If function are an abstraction for action, properties are abstraction for data. In order to achieve that goal, properties must behave in a way that is as close as possible to data, and avoid cases where it behave in a different way (an error if preferable when 100% compatibility with plain data isn't possible).

A user may want to use a property instead of plain data for the following reasons :

  • To use contract to protect what can be done with actual data.
  • To plug some debug informations or logging to know where the data is accessed/modified.
  • To provide a data like interface to user as it is often nicer to use than setter/getter.
  • To create write only/read only values.

This list is not exhaustive, but is clearly enough to show the need for a data oriented abstraction.

Properties methods

A property is defined as a method with the @property attribute :

struct Foo {
    @property
    ReturnType getter() {
        // Function body.
    }
 
    @property
    ReturnType setter(Parameter) {
        // Function body.
    }
}

Whenever the property is used as expression, it is automatically called. No exception.

Foo bar;
bar.getter;   // getter property is called.
bar.getter(); // Equivalent code if @property wasn't used.

If a property method have a parameter, it is a setter. This property can only be used on the left side of an assignation expression.

Foo bar;
bar.setter = 3; // setter property is called with 3 as parameter.
bar.setter(3);  // Equivalent code if @property wasn't used.

As a property is always called, explicitly calling it is either an error, or a call of the returned value if it is callable.

Foo bar;
bar.getter();   // getter is not callable (if ReturnType isn't callable).
                // Call what is returned by getter otherwise.
bar.getter()(); // Equivalent code if @property wasn't used.
bar.setter(3);  // Error.

As a property is always called, it has the type of its return value.

Foo bar;
static assert(is(typeof(bar.getter) == ReturnType));     // Pass
static assert(is(typeof(bar.getter()) == ReturnType));   // Equivalent code if @property wasn't used.
static assert(is(typeof(bar.setter = 3) == ReturnType)); // Pass
static assert(is(typeof(bar.setter(3)) == ReturnType));  // Equivalent code if @property wasn't used.

As a property is always called, taking its address take the address of the returned value (if allowed).

Foo bar;
&bar.getter; // Error, bar.getter isn't an lvalue.
&(bar.getter()); // Equivalent code if @property wasn't used.

Whenever the property is used as symbol (alias, alias template parameters), then the symbol is passed and it behave just like the property :

Foo bar;
auto foo(alias T)() {
    return T;
}
 
alias foo!(bar.getter) foobar; // No evaluation.
foobar(); // getter is evaluated.

Beware, getter can match both value and alias parameters, just like variable declarations. Same resolution rules apply.

Properties as free functions

When defined as free function, a property is intended to be used as UFCS. It means that a property must have 1 argument for a getter or 2 arguments for a setter.

@property
ReturnType getter(X a) {
    // Function body.
}
 
@property
ReturnType setter(X a, Y b) {
    // Function body.
}
 
X a;
Y b;
a.getter; // getter is called.
a.setter = b; // setter is called.

Operator overloading

Assignation can present itself is many shapes. In this case, the expression is rewritten to involve only simple assignation. For instance :

@property
ReturnType prop(X a) {
    // Function body.
}
 
@property
ReturnType prop(X a, uint i) {
    // Function body.
}
 
X a;
 
a.prop++;
// Become :
{ auto tmp = a.prop; a.prop = tmp + 1; return tmp; }();
{ auto tmp = a.prop(); a.prop(tmp + 1); return tmp; }(); // Equivalent code if @property wasn't used.
 
a.prop += 42;
// Become :
a.prop = a.prop + 42;
a.prop(a.prop() + 42); // Equivalent code if @property wasn't used.

Conclusion

This DIP propose a data oriented abstraction for D users. It has no special case whatsoever in order to avoid corner cases or bad interactions with other features.

Copyright

This document has been placed in the Public Domain.