DIP26

From D Wiki
Revision as of 13:09, 10 February 2013 by Robert (talk | contribs) (Behaviour like functions)
Jump to: navigation, search
Title: Properties with actual definition of the term property
DIP: 26
Version: 3
Status: Draft
Created: 2013-02-08
Last Modified: 2013-02-10
Author: Robert Klotzner
Links: DIP23

Abstract

This DIP establishes a very concrete definition of the term property and desired characteristics of properties and in turn establishes semantics of use for them. For optional parentheses, I would like to adopt the scheme already explained in DIP23.

This DIP is about changing the actual specification, not trying to make the implementation to match the specification, because in my view of things, the specification is the problem.

Properties in my proposal are no longer about optional parentheses or forbidden parentheses. Properties are a concept hat benefits from the fact, that parentheses are optional, but would work either way.

Reducing properties to functions with no parentheses is just not getting it right as we can see with the problems we have.

Functions are a good thing, why try to get rid of them? Why try to be like a field, if a very large part of the success of OOP is because it hides fields.

Funnily enough, if we embrace the concepts of OOP the current implementation is not that a bad thing to begin with, the specification is. So this proposal fixes things with a very good backwards compatibility to the current state of affairs.

Rationale

DIP23 and DIP24 both don't seem to have a clear concept what properties actually are, resulting in rules that destroy their original purpose (For example forbidding module level properties).

Properties as defined in this DIP are a way of encapsulation of fields of an entity (class, struct, module) in a way that the class/struct/module can actually control the access and thus encapsulates it, such that the field in reality might not even exist or be in a different format than presented, ...

The usual way of establishing this kind of encapsulation, explained in almost every book on OOP, is by the use of get/set methods and not exposing any fields in public. The problem with this approach is that the common case are trivial get/set methods which just return the internal fields value or set the fields value respectively. Also the naming of set/get methods is specified by convention making it hard for tools to detect what actually is a property and what is none if the convention is broken.

As can be seen the classical get/set methods and not exposing any field in public solves the above requirements perfectly, so this proposal defines properties to be nothing more as get/set methods with a well defined signature and some syntactic sugar.

Description

A property in D is defined as either a specially marked get method for read-only properties:

@property T foo();

or a specially marked set method for write-only methods:

@property void foo(T value);

or both for read/write properties.

For a default implementation and solving the problem of the boilerplate problem of traditional set/get methods the following syntax is suggested:

@property T foo;

which will be lowered by the compiler to:

private T __foo; // Just some internal name.

@property void foo(T value) {
    __foo=value;
}

@property T foo() {
    return __foo;
}

As it has been asked in the newsgroups a lot: Why not simply use a public field? The syntax for accessing them in D's current syntax for properties is the same anyway:

foo=someValue;
someValue=foo;

Well yes, but this is a pitfall. A public field simply offers no encapsulation by its very definition: It is a public field. This means:

  1. You can rely on the fact that the field really exists somewhere in the object - you can take its address, can use it as an lvalue.
  2. You can use them in expressions, which are currently not allowed for properties like:
      foo+=someValue;  foo/=someValue;
    

While the latter could be fixed, the former can't.

There have been some rejections to the @property field syntax on the news group: In short, if we don't have it, then properties are reduced to syntactic sugar for get/set methods and it should be very clearly stated in the documentation that despite their similar syntax to plain fields, they are not interchangeable at all and that one has to write the trivial set/get implementations or use a mixin.

Taking the address of a property

The unary & operator is free to take the address of the accessor method, just like it would for a normal function. You can not retrieve the address of the return value, because is an rvalue.

And yeah, just as in DIP23:

@property int a();
assert(is(typeof(a)==int));

Overloading @property methods

  1. Properties may not be overloaded with normal functions.
  2. Property-set methods might be overloaded freely with other one parameter set methods.
  3. Property-set-method overloads might take their parameter via ref, for performance reasons.

The following property definition would be illegal, as it violates encapsulation:

private T a_;
@property ref T a() {
    return a_;
}

It violates encapsulation just as a public field would and is thus, by definition, no property. Of course there are use cases for this, the front element of ranges comes to mind and they are of course still supported, just leave out "@property" in the above definition and you are all set. Semantic stays the same because of the optional-parentheses feature of functions:

private int a_;
ref int a() {
    return a_;
}
unittest {
    a=7;
    int c=a;
}

The only thing is, the moment you give up @property you loose encapsulation, if that is ok for your application, then you'll be fine. If not, a looser property definiton would not help you either.

No UFCS for properties

Properties protect the access to a field of an entity (class/struct/module), so they actually have to be defined within the entity they belong to, just as a field would. Even though D's module wide private access would make it technically possible to implement a property outside a class/struct, it sure is no big gain to actually allow this.

On the other hand, property methods implemented outside of the struct/class's containing module are not able to encapsulate any field, because they have only access to the public parts of the struct/class, which are directly accessible anyway.

So the very definition of properties, makes UFCS properties nonsensical and thus solves the ambiguity of module level properties.

Behaviour like functions

To resolve issues with functions returning functions/delegates and optional parantheses. Properties no longer pretend to be fields, they are functions offering convenience syntax. So it is perfectly fine to call a property accessor function with foo() or foo(arg) and is even mandatory if you want to call a returned delegate/function:

@property void function() foo();

unittest {
// Call the returned function:
foo()();
}

Reasoning: There is no value in pretending to be a field, as a field can always be made a function. The other way round is only possible for functions returning ref, but not for get/set functions. Also this way properties stay compatible with normal functions, which is at least in the case necessary where you switch from set/get property to function returning ref.

@property fields Details

The compiler must generate the standard get/set methods, taking parameters by value and returning them by value. From a quick look in the Qt documentation it seems that most properties are small any way. If pass by reference is desired, the implementations would have to be written by hand.

Upgrade path

Functions like:

@property ref front();

will only need to have @property removed. Every code using it will continue to work as it did.

For ranges, where front/back really are no functions (I haven't found a single one in std.algorithm or std.range!) they would have to be changed to actually be functions, even if trivial ones, to ensure full compatibility, as code is allowed to use front(). From my search in std.algorithm and std.range it is hard to believe that there is much code out there that would be concerned. The functions would be trivial and are easily inlined by the compiler, so no performance penalty either.

UFCS properties, should be pretty rare as UFCS is a pretty new feature, the ones that do exist seem to be mostly from the ref returning type, so simply remove the @property. (Search in std.algorithm) The ones that actually are of the set/get type should either be changed to members of an actually wrapping struct or if encapsulation is not desired, changed to a function returning ref.

Generic code won't break: It can use optional parentheses, but does not have to. Calling a delegate returned by a function/property function is always:

front()();

For functions marked with @property in an illegal way according to this DIP, the compiler will simply ignore the invalid @property annotation and will issue a deprecation warning.

Conclusion

It seems to be hard for people to embrace this concept, because even people who embrace properties as a means of encapsulation, seem to think that UFCS properties are needed, because of the way they are currently used in D. But the whole point of this DIP is to reduce properties to a means of encapsulation, resulting in a very clean and straight forward design, with no corner cases and with an astonigingly good backwards compatibility to the current implementation, causing very little and trivial code breakage.

Properties are functions, don't hide this fact. Trying to pretend that they are fields is doomed to fail, because functions are the more flexible tool, that actually is why we do encapsulation in OOP.

So this DIP is exactly the opposite approach of solving the property problem: Don't make them look more like fields, make public fields more look like functions.

Don't try to make functions look like fields, this can not work. Just do it the other way round: Don't ever make a field public, but use properties, as every OOP book tells you anyway. And all problems are solved, as far as I can see.

Copyright

This document has been placed in the Public Domain.