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.