Properties
Version | 2 |
Created | 2009-07-24 |
Status | {"Superseded"=>"DIP6 \"wikilink\""} |
Last modified | 2009-07-29 |
Author | Nick Sabalausky |
Abstract ——–
An alternate usage/definition syntax for properties.
Definition of Terms
The concepts of property and method need to be stated to maintain focus on the issue.
- property
-
An attribute of a object. Equivalent to ‘noun’ or ‘adjective’. An object has properties. One can assign value to a property and one retrieve the current value of a property. One cannot invoke a property.
- method
-
A behavior of an object. Equivalent to ‘verb’. An object does methods. One can invoke a method in order to manifest some capability of an object. One cannot assign a value to a behavior.
Any implementation of properties should be consistent with these conceptual views of the terms, such that a reader of D code that is using properties can see which members of a class are attributes and which are behaviors.
Note: Member variables are a subset of properties. They are the set of properties whose value is explicitly stored in a single declaration of RAM belonging to the object. Other properties might not have their value stored, but is instead derived at time of access based on the values of any number of other properties.
Rationale
D’s current property syntax has a number of problems:
- It is not possible for the class designer to prevent nonsensical uses, such as:
// Looks like an assignment, but isn't.
// Looks like 'writefln' is an attribute of the std.stdio module.
writefln = 5;
// An assignment, but is unclear.
// Looks like 'width' is a behavior of the 'widget' object.
widget.width(5);
class Foo
{
// private data here
void mutate(bool optionA=true)
{
// Modify foo in-place.
// "optionA" is some algorithm-adjusting option.
}
}
auto f = new Foo();
// Looks like you are setting the value of Foo's 'mutate' attribute.
f.mutate = false; // Extremely unclear and misleading.
- Allows ambiguous-looking code:
int delegate() foo() {
return delegate int(){ return 5; };
}
// Is this an int or a delegate?
auto x = foo();
// Is this a reference to foo, or a reference to the delegate returned by foo?
auto y = &foo
-
Cannot use +=, -=, etc.
-
IDE’s, debuggers and doc-generators (as well as programmers) cannot easily identify properties as properites and treat them accordingly (such as by including them in automatically-populated watch tables).
-
It is somewhat non-DRY in many cases, needing to duplicate the type and name (or some variant of the name) numerous times:
// Not an unreasonable example of current property definition.
// Can be improved somewhat, but mostly just with
// metaprogramming/mixins, and that's too ugly for such a common
// construct and it doesn't solve the other issues anyway.
private int _width; // type: 1, name: 1
int width() // type: 2, name: 2
{
return _width; // type: 2, name: 3
}
int width(int value) // type: 4, name: 4
{
return (_width = value); // type: 4, name: 5
}
As to the fact that in order to call parameterless function one can omit braces and this saves typing efforts, making code readable for humans is more important than making it easier to write.
Usage
class Foo
{
// Using full syntax
int width = int.init
{
get { return value; }
set { value = set; }
}
// Identical to width, but using syntactic sugar.
int height { get; set; }
// Call setter/getter from within setter/getter, by going through 'this'
int chattyValue
{
get
{
writefln("Hello");
return value;
}
set
{
value = set;
// Go through 'this' (implicitly in this case) to call getter.
auto dummy = chattyValue;
}
}
// Implicit 'value' need not be used and can be optimized away
char[] dbProp
{
get
{
return getFirstRowFromDB("SELECT myText FROM myTable WHERE id=0");
}
set
{
dbExec("UPDATE myTable WHERE id=0 SET myText='"~escape(set)~"'");
}
}
int readOnly { get; }
char[] writeOnly ( set; ) // Rare, but allowed
void changeReadOnly(bool makeItBig)
{
readOnly = makeItBig? 9999 : 1;
}
void displayWriteOnly()
{
writefln(writeOnly);
}
int getFive()
{
return 5;
}
int delegate() generatorDg { get; set; }
// For illustrative purposes:
int delegate() getGeneratorDg() { return generatorDg; }
}
unittest
{
auto f = new Foo();
// Foo.width
assert(f.width == int.init);
f.width = 20;
f.width(20); // ERROR! An int is not callable!
assert(f.width == 20);
f.width += 5; // Calls getter, saves it in a temp, does += on temp, calls setter with temp.
f.width++; // Works similarly
assert(f.width == 26);
auto widthRef = &f.width; // A "struct Property!(int)" of some sort exposing get/set as delegates
// Foo.chattyValue
assert(f.chattyValue == int.init); // Displays "Hello"
f.chattyValue = 20; // Displays "Hello"
assert(f.chattyValue == 20); // Displays "Hello"
// Foo.dbProp
f.dbProp = "hello";
assert(f.dbProp == "hello");
// Foo.readOnly
assert(f.readOnly == int.init);
f.changeReadOnly = true; // ERROR! Not a variable or property!
f.changeReadOnly(true);
assert(f.readOnly == 9999);
f.readOnly = 5; // ERROR! Writing to this prop is private-only.
auto roRef = &r.readOnly; // A "struct Property!(int)" of some sort exposing just get
// Foo.writeOnly
assert(f.writeOnly == int.init); // ERROR! Reading this prop is private-only.
f.writeOnly = "Whee";
f.displayWriteOnly(); // Displays "Whee"
// Misc
writefln = "fizzle"; // ERROR! writefln is a function, not an lvalue!
auto five = f.getFive; // ERROR! Not a variable or property!
auto five = f.getFive();
// Foo.generatorDg
auto myDg = int delegate() { return 42; }
f.generatorDg = myDg;
auto g = &f.generatorDg; // A "struct Property!(int delegate())" of some sort exposing get/set
auto g = f.generatorDg; // g == myDg
auto g = f.generatorDg(); // g == 42
auto g = f.generatorDg()(); // ERROR! An int is not callable!
auto g = &f.getGeneratorDg; // g is a delegate that returns myDg
auto g = f.getGeneratorDg; // ERROR! Not a variable or property!
auto g = f.getGeneratorDg(); // g == myDg
auto g = f.getGeneratorDg()(); // g == 42
}
Description
Much of it should be self-explanatory from the example above, but here are additional notes:
-
Properties are defined just like variables, except ending with
{...}
instead of;
. This keeps the definition syntax as consistent as possible while still being easy for the parser to distinguish from variable declarations. -
Each property automatically has it’s own private storage named
value
which eliminates the need for a redundant declaration with arbitrarily-chosen naming conventions and allows the property itself to be easily renamed without any change to the get/set code (much like constructors being namedthis
). Thisvalue
is never accessed directly from outside the setter/getter (except as described below), and it always means exactly the same thing, so there’s no need to be able to name it manually (unlike, for instance, function parameters). -
Direct access to the implicit underlying private
value
is typically limited to within the property’s definition, so it should be easy to detect whether it can safely be optimized away. For cases where it is necessary for this value to be accessed directly by other members of the class (which should only ever be needed for optimization purposes), that access can be obtained through traits (NEED HELP - actual syntax/semantics of this). But trying to do so if it’s been optimized away (ie, never accessed within the property’s get/set) is a (again, fairly easy-to-detect) error because if the getter/setter aren’t accessingvalue
, it doesn’t make sense for anything else to either. -
Currently, taking the address of a property results in a delegate. Under this proposal, it results in a templated struct containing two delegates, a setter and a getter. (NEED HELP) This should have some way of handling read-only and write-only properties, maybe through some hierarchy of structs/interfaces or fancy templating, or setting the inaccessible ones to null?
-
(NEED HELP) How to handle whether or not subclasses can write to read-only props, get at
value
through traits, etc? Use optional access qualifiers on the get and set? What are the defaults? -
The tokens
get
andset
are not keywords, they just have this special meaning inside a property definition. But, they lose that special meaning again within the actual set/get bodies (except as described below). -
Within the setter,
set
is used to refer to the new value. So a default setter would beset
{value
=
set;}
. This has much the same reasoning as the implicitvalue
: It always means the same thing (unlike the parameters of normal functions), so it’s better to standardize it than to require extra verbosity just for the sake of what essentially amounts to arbitrary personal customization. (And if you *really* want to change it you can always do set{auto
myFancyName=set;
...}
.) -
TODO: define inheritance semantic.
Comments
Please use the ‘digitalmars.D’ newsgroup for comments and discussion:
The existing comments have been moved there.
Copyright
This document has been placed in the Public Domain.