Properties in the D programming language
Posted on by Idorobots
Just to evangelize D a little and increase my code/crap ratio, let's pretend we develop a library in C++ that contains this class:
class SomeMetaVariables {
public:
std::string foo;
bool bar;
};
// ... Somewhere in the client code:
SomeMetaVariables baz;
baz.foo = "foo";
baz.bar = true;
Our library is quite successful and many people are using SomeMetaVariables
despite its obvious flaws. Now, say we get many requests for additional functionality, for example: "Make bar
true
only when foo
is set to "foo" and other way arround." "Well, ok." - we say and commit this new version of SomeMetaVariables
:
class SomeMetaVariables {
std::string foo;
bool bar;
public:
std::string getFoo() {
return foo;
}
std::string setFoo(std::string newFoo) {
foo = newFoo;
bar = (foo == "foo");
return foo;
}
bool getBar() {
return bar;
}
bool setBar(bool newBar) {
bar = newBar;
foo = bar ? "foo" : "";
return bar;
}
};
We implemented the requested feature, but SomeMetaVariables
' interface has changed... "But why are you mad clients? You asked for it!" - cries the C++ developer. Clearly, we should have used setter/getter routines in the first place not to break any code in case of such feature requests. However, this approach introduces lots of additional, dead code:
class SomeMetaVariables {
std::string foo;
bool bar;
// <bloat>
public:
std::string getFoo() {
return foo;
}
std::string setFoo(std::string newFoo) {
return foo = newFoo;
}
bool getBar() {
return bar;
}
bool setBar(bool newBar) {
return bar = newBar;
}
// </bloat>
};
That's 400% more code that does exactly as much as the earlier version! And this is a CONVENTION in Java!
D addresses this problem differently, using properties:
class SomeMetaVariables {
private string foo_;
private bool bar_;
@property {
string foo() {
return foo_;
}
string foo(string newFoo) {
return foo_ = newFoo;
}
bool bar() {
return bar_;
}
bool bar(bool newBar) {
return bar_ = newBar;
}
}
}
It's clean, it's elegant, it doesn't change the interface. It reminds of Python's property()
builtin:
class SomeMetaVariables(object)
def __init__(self)
self._foo = ""
self._bar = false
def get_foo(self)
return self._foo
def set_foo(self, newFoo)
self._foo = newFoo
return self._foo
foo = property(get_foo, set_foo)
def get_bar(self)
return self._bar
def set_bar(self, newBar)
self._bar = newBar
return self._bar
bar = property(get_bar, set_bar)
# ...later:
baz = SomeMetaVariables()
baz.bar = true
baz.foo = "foo"
...which can be reproduced in D:
import std.traits : ReturnType;
mixin template Property(string name, alias getter, alias setter,
string gtype = ReturnType!(getter).stringof,
string stype = ReturnType!(setter).stringof)
{
mixin( "@property {"
~gtype~" "~name~"() {"
"return getter();"
"}"
~stype~" "~name~"("~gtype~" newVal) {"
"return setter(newVal);"
"}"
"}");
}
...using mixin templates that use mixins and traits! This code isn't normal, but in D it is.
Finally, the version with both setters/getters and properties that implement 'highly requested feature' mentioned earlier:
class SomeMetaVariables {
private string foo_;
private bool bar_;
string getFoo() {
return foo_;
}
string setFoo(string newFoo) {
foo_ = newFoo;
bar_ = (foo_ == "foo");
return foo_;
}
mixin Property!("foo", getFoo, setFoo);
bool getBar() {
return bar_;
}
bool setBar(bool newBar) {
bar_ = newBar;
foo_ = bar_ ? "foo" : "";
return bar_;
}
mixin Property!("bar", getBar, setBar);
}
// ...
auto baz = new SomeMetaVariables();
baz.foo = "foo";
assert(baz.bar == true);
baz.setBar(false);
Didn't break any client code and still implemented the feature. It's flexible and elegant. I liek D.